class PokemonGoBot(object): @property def position(self): return self.api._position_lat, self.api._position_lng, 0 @position.setter def position(self, position_tuple): self.api._position_lat, self.api._position_lng, self.api._position_alt = position_tuple @property def player_data(self): """ Returns the player data as received from the API. :return: The player data. :rtype: dict """ return self._player def __init__(self, config): self.config = config self.fort_timeouts = dict() self.pokemon_list = json.load( open(os.path.join('data', 'pokemon.json')) ) self.item_list = json.load(open(os.path.join('data', 'items.json'))) self.metrics = Metrics(self) self.latest_inventory = None self.cell = None self.recent_forts = [None] * config.forts_max_circle_size self.tick_count = 0 self.softban = False self.start_position = None self.last_map_object = None self.last_time_map_object = 0 self.logger = logging.getLogger(type(self).__name__) # Make our own copy of the workers for this instance self.workers = [] def start(self): self._setup_event_system() self._setup_logging() self._setup_api() random.seed() def _setup_event_system(self): handlers = [LoggingHandler()] if self.config.websocket_server_url: if self.config.websocket_start_embedded_server: self.sio_runner = SocketIoRunner(self.config.websocket_server_url) self.sio_runner.start_listening_async() websocket_handler = SocketIoHandler( self, self.config.websocket_server_url ) handlers.append(websocket_handler) if self.config.websocket_remote_control: remote_control = WebsocketRemoteControl(self).start() self.event_manager = EventManager(*handlers) self._register_events() if self.config.show_events: self.event_manager.event_report() sys.exit(1) # Registering event: # self.event_manager.register_event("location", parameters=['lat', 'lng']) # # Emitting event should be enough to add logging and send websocket # message: : # self.event_manager.emit('location', 'level'='info', data={'lat': 1, 'lng':1}), def _register_events(self): self.event_manager.register_event( 'location_found', parameters=('position', 'location') ) self.event_manager.register_event('api_error') self.event_manager.register_event('config_error') self.event_manager.register_event('login_started') self.event_manager.register_event('login_failed') self.event_manager.register_event('login_successful') self.event_manager.register_event('set_start_location') self.event_manager.register_event('load_cached_location') self.event_manager.register_event('location_cache_ignored') self.event_manager.register_event( 'position_update', parameters=( 'current_position', 'last_position', 'distance', # optional 'distance_unit' # optional ) ) self.event_manager.register_event('location_cache_error') self.event_manager.register_event('bot_start') self.event_manager.register_event('bot_exit') # sleep stuff self.event_manager.register_event( 'next_sleep', parameters=('time',) ) self.event_manager.register_event( 'bot_sleep', parameters=('time_in_seconds',) ) # fort stuff self.event_manager.register_event( 'spun_fort', parameters=( 'fort_id', 'latitude', 'longitude' ) ) self.event_manager.register_event( 'lured_pokemon_found', parameters=( 'fort_id', 'fort_name', 'encounter_id', 'latitude', 'longitude' ) ) self.event_manager.register_event( 'moving_to_fort', parameters=( 'fort_name', 'distance' ) ) self.event_manager.register_event( 'moving_to_lured_fort', parameters=( 'fort_name', 'distance', 'lure_distance' ) ) self.event_manager.register_event( 'spun_pokestop', parameters=( 'pokestop', 'exp', 'items' ) ) self.event_manager.register_event( 'pokestop_empty', parameters=('pokestop',) ) self.event_manager.register_event( 'pokestop_out_of_range', parameters=('pokestop',) ) self.event_manager.register_event( 'pokestop_on_cooldown', parameters=('pokestop', 'minutes_left') ) self.event_manager.register_event( 'unknown_spin_result', parameters=('status_code',) ) self.event_manager.register_event('pokestop_searching_too_often') self.event_manager.register_event('arrived_at_fort') # pokemon stuff self.event_manager.register_event( 'catchable_pokemon', parameters=( 'pokemon_id', 'spawn_point_id', 'encounter_id', 'latitude', 'longitude', 'expiration_timestamp_ms' ) ) self.event_manager.register_event( 'pokemon_appeared', parameters=( 'pokemon', 'cp', 'iv', 'iv_display', ) ) self.event_manager.register_event( 'pokemon_catch_rate', parameters=( 'catch_rate', 'berry_name', 'berry_count' ) ) self.event_manager.register_event( 'threw_berry', parameters=( 'berry_name', 'new_catch_rate' ) ) self.event_manager.register_event( 'threw_pokeball', parameters=( 'pokeball', 'success_percentage', 'count_left' ) ) self.event_manager.register_event( 'pokemon_fled', parameters=('pokemon',) ) self.event_manager.register_event( 'pokemon_vanished', parameters=('pokemon',) ) self.event_manager.register_event( 'pokemon_caught', parameters=( 'pokemon', 'cp', 'iv', 'iv_display', 'exp' ) ) self.event_manager.register_event( 'pokemon_evolved', parameters=('pokemon', 'iv', 'cp') ) self.event_manager.register_event( 'pokemon_evolve_fail', parameters=('pokemon',) ) self.event_manager.register_event('skip_evolve') self.event_manager.register_event('threw_berry_failed', parameters=('status_code',)) self.event_manager.register_event('vip_pokemon') # level up stuff self.event_manager.register_event( 'level_up', parameters=( 'previous_level', 'current_level' ) ) self.event_manager.register_event( 'level_up_reward', parameters=('items',) ) # lucky egg self.event_manager.register_event( 'used_lucky_egg', parameters=('amount_left',) ) self.event_manager.register_event('lucky_egg_error') # softban self.event_manager.register_event('softban') self.event_manager.register_event('softban_fix') self.event_manager.register_event('softban_fix_done') # egg incubating self.event_manager.register_event( 'incubate_try', parameters=( 'incubator_id', 'egg_id' ) ) self.event_manager.register_event( 'incubate', parameters=('distance_in_km',) ) self.event_manager.register_event( 'next_egg_incubates', parameters=('distance_in_km',) ) self.event_manager.register_event('incubator_already_used') self.event_manager.register_event('egg_already_incubating') self.event_manager.register_event( 'egg_hatched', parameters=( 'pokemon', 'cp', 'iv', 'exp', 'stardust', 'candy' ) ) # discard item self.event_manager.register_event( 'item_discarded', parameters=( 'amount', 'item', 'maximum' ) ) self.event_manager.register_event( 'item_discard_fail', parameters=('item',) ) # inventory self.event_manager.register_event('inventory_full') # release self.event_manager.register_event( 'keep_best_release', parameters=( 'amount', 'pokemon', 'criteria' ) ) self.event_manager.register_event( 'future_pokemon_release', parameters=( 'pokemon', 'cp', 'iv', 'below_iv', 'below_cp', 'cp_iv_logic' ) ) self.event_manager.register_event( 'pokemon_release', parameters=('pokemon', 'cp', 'iv') ) # polyline walker self.event_manager.register_event( 'polyline_request', parameters=('url',) ) # cluster self.event_manager.register_event( 'found_cluster', parameters=( 'num_points', 'forts', 'radius', 'distance' ) ) self.event_manager.register_event( 'arrived_at_cluster', parameters=( 'forts', 'radius' ) ) # rename self.event_manager.register_event( 'rename_pokemon', parameters=( 'old_name', 'current_name' ) ) self.event_manager.register_event( 'pokemon_nickname_invalid', parameters=('nickname',) ) self.event_manager.register_event('unset_pokemon_nickname') # Move To map pokemon self.event_manager.register_event( 'move_to_map_pokemon_fail', parameters=('message',) ) self.event_manager.register_event( 'move_to_map_pokemon_updated_map', parameters=('lat', 'lon') ) self.event_manager.register_event( 'move_to_map_pokemon_teleport_to', parameters=('poke_name', 'poke_dist', 'poke_lat', 'poke_lon', 'disappears_in') ) self.event_manager.register_event( 'move_to_map_pokemon_encounter', parameters=('poke_name', 'poke_dist', 'poke_lat', 'poke_lon', 'disappears_in') ) self.event_manager.register_event( 'move_to_map_pokemon_move_towards', parameters=('poke_name', 'poke_dist', 'poke_lat', 'poke_lon', 'disappears_in') ) self.event_manager.register_event( 'move_to_map_pokemon_teleport_back', parameters=('last_lat', 'last_lon') ) def tick(self): self.health_record.heartbeat() self.cell = self.get_meta_cell() self.tick_count += 1 # Check if session token has expired self.check_session(self.position[0:2]) for worker in self.workers: if worker.work() == WorkerResult.RUNNING: return def open_bag(self): response = self.get_inventory() self.inventory = list() own_pokemon_list = { int(x['Number']): {'name': x['Name'], 'candies_needed': x.get('Next Evolution Requirements', {'Amount': 18446744073709551615 }).get('Amount'), 'count': 0} for x in self.pokemon_list } inventory_items = response.get('responses', {}).get('GET_INVENTORY', {}).get( 'inventory_delta', {}).get('inventory_items', {}) if inventory_items: for item in inventory_items: item_info = item.get('inventory_item_data', {}) if {"pokemon_family"}.issubset(set(item_info.keys())): pokemon_family = item['inventory_item_data']['pokemon_family'] pokemon_data = own_pokemon_list[pokemon_family['family_id']] pokemon_data['candies_number'] = pokemon_family['candy'] if {"pokemon_data"}.issubset(set(item_info.keys())): pokemon_id = item['inventory_item_data']['pokemon_data'].get('pokemon_id') if pokemon_id: pokemon = own_pokemon_list[pokemon_id] pokemon['count'] += 1 with open('pokemons_process.csv', 'wb') as csvfile: spamwriter = csv.writer( csvfile, delimiter=str(u';'), quotechar=str(u'"'), quoting=csv.QUOTE_MINIMAL ) spamwriter.writerow([ 'pokemon', 'evolution_cost', 'total_candies', 'total_evolutions', 'pokemons_available', 'evolutions_available', 'advice' ]) advice = { True:'{} evolutions are unused. Catch more. Relax restrictions for this pokemon in config.json ("release" field)', False: '{} pokemons are unused. Release more. Enhance restrictions for this pokemon in config.json ("release" field)' } total_upgrade_count = 0 for id, pokemon_info in own_pokemon_list.iteritems(): pokemon_count = pokemon_info['count'] current_candies_number = pokemon_info.get('candies_number', 0) candies_needed = pokemon_info['candies_needed'] if pokemon_count or (current_candies_number): if candies_needed == 18446744073709551615: spamwriter.writerow([ pokemon_info['name'], 'last form', current_candies_number, '-', pokemon_count, '-', advice[False].format(pokemon_count) ]) else: # if count and (candies_needed != 18446744073709551615): current_candies_number = pokemon_info.get('candies_number', 0) count_upgrade = current_candies_number/pokemon_info['candies_needed'] total_upgrade_count += min(pokemon_count, count_upgrade) candies_left = candies_needed - candies_needed*current_candies_number pokemons_left = pokemon_count - total_upgrade_count if candies_left: advice_string = advice[True].format(candies_left) else: advice_string = advice[False].format(pokemons_left) spamwriter.writerow([ pokemon_info['name'], candies_needed, current_candies_number, count_upgrade, pokemon_count, total_upgrade_count, advice_string ]) spamwriter.writerow([ 'Total evolutons can be made: {}'.format(total_upgrade_count) ]) def get_meta_cell(self): location = self.position[0:2] cells = self.find_close_cells(*location) # Combine all cells into a single dict of the items we care about. forts = [] wild_pokemons = [] catchable_pokemons = [] for cell in cells: if "forts" in cell and len(cell["forts"]): forts += cell["forts"] if "wild_pokemons" in cell and len(cell["wild_pokemons"]): wild_pokemons += cell["wild_pokemons"] if "catchable_pokemons" in cell and len(cell["catchable_pokemons"]): catchable_pokemons += cell["catchable_pokemons"] # If there are forts present in the cells sent from the server or we don't yet have any cell data, return all data retrieved if len(forts) > 1 or not self.cell: return { "forts": forts, "wild_pokemons": wild_pokemons, "catchable_pokemons": catchable_pokemons } # If there are no forts present in the data from the server, keep our existing fort data and only update the pokemon cells. else: return { "forts": self.cell["forts"], "wild_pokemons": wild_pokemons, "catchable_pokemons": catchable_pokemons } def update_web_location(self, cells=[], lat=None, lng=None, alt=None): # we can call the function with no arguments and still get the position # and map_cells if lat is None: lat = self.api._position_lat if lng is None: lng = self.api._position_lng if alt is None: alt = 0 if cells == []: location = self.position[0:2] cells = self.find_close_cells(*location) # insert detail info about gym to fort for cell in cells: if 'forts' in cell: for fort in cell['forts']: if fort.get('type') != 1: response_gym_details = self.api.get_gym_details( gym_id=fort.get('id'), player_latitude=lng, player_longitude=lat, gym_latitude=fort.get('latitude'), gym_longitude=fort.get('longitude') ) fort['gym_details'] = response_gym_details.get( 'responses', {} ).get('GET_GYM_DETAILS', None) user_data_cells = "data/cells-%s.json" % self.config.username with open(user_data_cells, 'w') as outfile: json.dump(cells, outfile) user_web_location = os.path.join( 'web', 'location-%s.json' % self.config.username ) # alt is unused atm but makes using *location easier try: with open(user_web_location, 'w') as outfile: json.dump({ 'lat': lat, 'lng': lng, 'alt': alt, 'cells': cells }, outfile) except IOError as e: self.logger.info('[x] Error while opening location file: %s' % e) user_data_lastlocation = os.path.join( 'data', 'last-location-%s.json' % self.config.username ) try: with open(user_data_lastlocation, 'w') as outfile: json.dump({'lat': lat, 'lng': lng, 'start_position': self.start_position}, outfile) except IOError as e: self.logger.info('[x] Error while opening location file: %s' % e) def find_close_cells(self, lat, lng): cellid = get_cell_ids(lat, lng) timestamp = [0, ] * len(cellid) response_dict = self.get_map_objects(lat, lng, timestamp, cellid) map_objects = response_dict.get( 'responses', {} ).get('GET_MAP_OBJECTS', {}) status = map_objects.get('status', None) map_cells = [] if status and status == 1: map_cells = map_objects['map_cells'] position = (lat, lng, 0) map_cells.sort( key=lambda x: distance( lat, lng, x['forts'][0]['latitude'], x['forts'][0]['longitude']) if x.get('forts', []) else 1e6 ) return map_cells def _setup_logging(self): # log settings # log format if self.config.debug: log_level = logging.DEBUG logging.getLogger("requests").setLevel(logging.DEBUG) logging.getLogger("websocket").setLevel(logging.DEBUG) logging.getLogger("socketio").setLevel(logging.DEBUG) logging.getLogger("engineio").setLevel(logging.DEBUG) logging.getLogger("socketIO-client").setLevel(logging.DEBUG) logging.getLogger("pgoapi").setLevel(logging.DEBUG) logging.getLogger("rpc_api").setLevel(logging.DEBUG) else: log_level = logging.ERROR logging.getLogger("requests").setLevel(logging.ERROR) logging.getLogger("websocket").setLevel(logging.ERROR) logging.getLogger("socketio").setLevel(logging.ERROR) logging.getLogger("engineio").setLevel(logging.ERROR) logging.getLogger("socketIO-client").setLevel(logging.ERROR) logging.getLogger("pgoapi").setLevel(logging.ERROR) logging.getLogger("rpc_api").setLevel(logging.ERROR) logging.basicConfig( level=log_level, format='%(asctime)s [%(name)10s] [%(levelname)s] %(message)s' ) def check_session(self, position): # Check session expiry if self.api._auth_provider and self.api._auth_provider._ticket_expire: # prevent crash if return not numeric value if not self.is_numeric(self.api._auth_provider._ticket_expire): self.logger.info("Ticket expired value is not numeric", 'yellow') return remaining_time = \ self.api._auth_provider._ticket_expire / 1000 - time.time() if remaining_time < 60: self.event_manager.emit( 'api_error', sender=self, level='info', formatted='Session stale, re-logging in.' ) position = self.position self.api = ApiWrapper() self.position = position self.login() self.api.activate_signature(self.get_encryption_lib()) @staticmethod def is_numeric(s): try: float(s) return True except ValueError: return False def login(self): self.event_manager.emit( 'login_started', sender=self, level='info', formatted="Login procedure started." ) lat, lng = self.position[0:2] self.api.set_position(lat, lng, 0) while not self.api.login( self.config.auth_service, str(self.config.username), str(self.config.password)): self.event_manager.emit( 'login_failed', sender=self, level='info', formatted="Login error, server busy. Waiting 10 seconds to try again." ) time.sleep(10) self.event_manager.emit( 'login_successful', sender=self, level='info', formatted="Login successful." ) def get_encryption_lib(self): if _platform == "linux" or _platform == "linux2" or _platform == "darwin": file_name = 'encrypt.so' elif _platform == "Windows" or _platform == "win32": # Check if we are on 32 or 64 bit if sys.maxsize > 2**32: file_name = 'encrypt_64.dll' else: file_name = 'encrypt.dll' if self.config.encrypt_location == '': path = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir)) else: path = self.config.encrypt_location full_path = path + '/'+ file_name if not os.path.isfile(full_path): self.logger.error(file_name + ' is not found! Please place it in the bots root directory or set libencrypt_location in config.') self.logger.info('Platform: '+ _platform + ' Encrypt.so directory: '+ path) sys.exit(1) else: self.logger.info('Found '+ file_name +'! Platform: ' + _platform + ' Encrypt.so directory: ' + path) return full_path def _setup_api(self): # instantiate pgoapi self.api = ApiWrapper() # provide player position on the earth self._set_starting_position() self.login() # chain subrequests (methods) into one RPC call self._print_character_info() self.api.activate_signature(self.get_encryption_lib()) self.logger.info('') self.update_inventory() # send empty map_cells and then our position self.update_web_location() self.open_bag() def _print_character_info(self): # get player profile call # ---------------------- response_dict = self.api.get_player() # print('Response dictionary: \n\r{}'.format(json.dumps(response_dict, indent=2))) currency_1 = "0" currency_2 = "0" if response_dict: self._player = response_dict['responses']['GET_PLAYER']['player_data'] player = self._player else: self.logger.info( "The API didn't return player info, servers are unstable - " "retrying.", 'red' ) sleep(5) self._print_character_info() # @@@ TODO: Convert this to d/m/Y H:M:S creation_date = datetime.datetime.fromtimestamp( player['creation_timestamp_ms'] / 1e3) creation_date = creation_date.strftime("%Y/%m/%d %H:%M:%S") pokecoins = '0' stardust = '0' items_stock = self.current_inventory() if 'amount' in player['currencies'][0]: pokecoins = player['currencies'][0]['amount'] if 'amount' in player['currencies'][1]: stardust = player['currencies'][1]['amount'] self.logger.info('') self.logger.info('--- {username} ---'.format(**player)) self.get_player_info() self.logger.info( 'Pokemon Bag: {}/{}'.format( self.get_inventory_count('pokemon'), player['max_pokemon_storage'] ) ) self.logger.info( 'Items: {}/{}'.format( self.get_inventory_count('item'), player['max_item_storage'] ) ) self.logger.info( 'Stardust: {}'.format(stardust) + ' | Pokecoins: {}'.format(pokecoins) ) # Items Output self.logger.info( 'PokeBalls: ' + str(items_stock[1]) + ' | GreatBalls: ' + str(items_stock[2]) + ' | UltraBalls: ' + str(items_stock[3])) self.logger.info( 'RazzBerries: ' + str(items_stock[701]) + ' | BlukBerries: ' + str(items_stock[702]) + ' | NanabBerries: ' + str(items_stock[703])) self.logger.info( 'LuckyEgg: ' + str(items_stock[301]) + ' | Incubator: ' + str(items_stock[902]) + ' | TroyDisk: ' + str(items_stock[501])) self.logger.info( 'Potion: ' + str(items_stock[101]) + ' | SuperPotion: ' + str(items_stock[102]) + ' | HyperPotion: ' + str(items_stock[103]) + ' | MaxPotion: ' + str(items_stock[104])) self.logger.info( 'Incense: ' + str(items_stock[401]) + ' | IncenseSpicy: ' + str(items_stock[402]) + ' | IncenseCool: ' + str(items_stock[403])) self.logger.info( 'Revive: ' + str(items_stock[201]) + ' | MaxRevive: ' + str(items_stock[202])) self.logger.info('') def use_lucky_egg(self): return self.api.use_item_xp_boost(item_id=301) def get_inventory(self): if self.latest_inventory is None: self.latest_inventory = self.api.get_inventory() return self.latest_inventory def update_inventory(self): response = self.get_inventory() self.inventory = list() inventory_items = response.get('responses', {}).get('GET_INVENTORY', {}).get( 'inventory_delta', {}).get('inventory_items', {}) if inventory_items: for item in inventory_items: item_info = item.get('inventory_item_data', {}).get('item', {}) if {"item_id", "count"}.issubset(set(item_info.keys())): self.inventory.append(item['inventory_item_data']['item']) def current_inventory(self): inventory_req = self.get_inventory() inventory_dict = inventory_req['responses']['GET_INVENTORY'][ 'inventory_delta']['inventory_items'] user_web_inventory = 'web/inventory-%s.json' % self.config.username with open(user_web_inventory, 'w') as outfile: json.dump(inventory_dict, outfile) # get player items stock # ---------------------- items_stock = {x.value: 0 for x in list(Item)} for item in inventory_dict: item_dict = item.get('inventory_item_data', {}).get('item', {}) item_count = item_dict.get('count') item_id = item_dict.get('item_id') if item_count and item_id: if item_id in items_stock: items_stock[item_id] = item_count return items_stock def item_inventory_count(self, id): inventory_req = self.get_inventory() inventory_dict = inventory_req['responses'][ 'GET_INVENTORY']['inventory_delta']['inventory_items'] if id == 'all': return self._all_items_inventory_count(inventory_dict) else: return self._item_inventory_count_per_id(id, inventory_dict) def _item_inventory_count_per_id(self, id, inventory_dict): item_count = 0 for item in inventory_dict: item_dict = item.get('inventory_item_data', {}).get('item', {}) item_id = item_dict.get('item_id', False) item_count = item_dict.get('count', False) if item_id == int(id) and item_count: return item_count return 0 def _all_items_inventory_count(self, inventory_dict): item_count_dict = {} for item in inventory_dict: item_dict = item.get('inventory_item_data', {}).get('item', {}) item_id = item_dict.get('item_id', False) item_count = item_dict.get('count', False) if item_id and item_count: item_count_dict[item_id] = item_count return item_count_dict def _set_starting_position(self): self.event_manager.emit( 'set_start_location', sender=self, level='info', formatted='Setting start location.' ) has_position = False if self.config.test: # TODO: Add unit tests return if self.config.location: location_str = self.config.location location = self.get_pos_by_name(location_str.replace(" ", "")) msg = "Location found: {location} {position}" self.event_manager.emit( 'location_found', sender=self, level='info', formatted=msg, data={ 'location': location_str, 'position': location } ) self.api.set_position(*location) self.event_manager.emit( 'position_update', sender=self, level='info', formatted="Now at {current_position}", data={ 'current_position': self.position, 'last_position': '', 'distance': '', 'distance_unit': '' } ) self.start_position = self.position has_position = True if self.config.location_cache: try: # save location flag used to pull the last known location from # the location.json self.event_manager.emit( 'load_cached_location', sender=self, level='debug', formatted='Loading cached location...' ) with open('data/last-location-%s.json' % self.config.username) as f: location_json = json.load(f) location = ( location_json['lat'], location_json['lng'], 0.0 ) # If location has been set in config, only use cache if starting position has not differed if has_position and 'start_position' in location_json: last_start_position = tuple(location_json.get('start_position', [])) # Start position has to have been set on a previous run to do this check if last_start_position and last_start_position != self.start_position: msg = 'Going to a new place, ignoring cached location.' self.event_manager.emit( 'location_cache_ignored', sender=self, level='debug', formatted=msg ) return self.api.set_position(*location) self.event_manager.emit( 'position_update', sender=self, level='debug', formatted='Loaded location {current_position} from cache', data={ 'current_position': location, 'last_position': '', 'distance': '', 'distance_unit': '' } ) has_position = True except Exception: if has_position is False: sys.exit( "No cached Location. Please specify initial location." ) self.event_manager.emit( 'location_cache_error', sender=self, level='debug', formatted='Parsing cached location failed.' ) def get_pos_by_name(self, location_name): # Check if the given location is already a coordinate. if ',' in location_name: possible_coordinates = re.findall( "[-]?\d{1,3}[.]\d{3,7}", location_name ) if len(possible_coordinates) == 2: # 2 matches, this must be a coordinate. We'll bypass the Google # geocode so we keep the exact location. self.logger.info( '[x] Coordinates found in passed in location, ' 'not geocoding.' ) return float(possible_coordinates[0]), float(possible_coordinates[1]), float("0.0") geolocator = GoogleV3(api_key=self.config.gmapkey) loc = geolocator.geocode(location_name, timeout=10) return float(loc.latitude), float(loc.longitude), float(loc.altitude) def heartbeat(self): # Remove forts that we can now spin again. self.fort_timeouts = {id: timeout for id, timeout in self.fort_timeouts.iteritems() if timeout >= time.time() * 1000} request = self.api.create_request() request.get_player() request.check_awarded_badges() request.call() self.update_web_location() # updates every tick def get_inventory_count(self, what): response_dict = self.get_inventory() inventory_items = response_dict.get('responses', {}).get('GET_INVENTORY', {}).get( 'inventory_delta', {}).get('inventory_items', {}) if inventory_items: pokecount = 0 itemcount = 1 for item in inventory_items: if 'inventory_item_data' in item: if 'pokemon_data' in item['inventory_item_data']: pokecount += 1 itemcount += item['inventory_item_data'].get('item', {}).get('count', 0) if 'pokemon' in what: return pokecount if 'item' in what: return itemcount return '0' def get_player_info(self): response_dict = self.get_inventory() inventory_items = response_dict.get('responses', {}).get('GET_INVENTORY', {}).get( 'inventory_delta', {}).get('inventory_items', {}) if inventory_items: pokecount = 0 itemcount = 1 for item in inventory_items: # print('item {}'.format(item)) playerdata = item.get('inventory_item_data', {}).get('player_stats') if playerdata: nextlvlxp = (int(playerdata.get('next_level_xp', 0)) - int(playerdata.get('experience', 0))) if 'level' in playerdata and 'experience' in playerdata: self.logger.info( 'Level: {level}'.format( **playerdata) + ' (Next Level: {} XP)'.format( nextlvlxp) + ' (Total: {experience} XP)' ''.format(**playerdata)) if 'pokemons_captured' in playerdata and 'poke_stop_visits' in playerdata: self.logger.info( 'Pokemon Captured: ' '{pokemons_captured}'.format( **playerdata) + ' | Pokestops Visited: ' '{poke_stop_visits}'.format( **playerdata)) def has_space_for_loot(self): number_of_things_gained_by_stop = 5 enough_space = ( self.get_inventory_count('item') < self._player['max_item_storage'] - number_of_things_gained_by_stop ) return enough_space def get_forts(self, order_by_distance=False): forts = [fort for fort in self.cell['forts'] if 'latitude' in fort and 'type' in fort] if order_by_distance: forts.sort(key=lambda x: distance( self.position[0], self.position[1], x['latitude'], x['longitude'] )) return forts def get_map_objects(self, lat, lng, timestamp, cellid): if time.time() - self.last_time_map_object < self.config.map_object_cache_time: return self.last_map_object self.last_map_object = self.api.get_map_objects( latitude=f2i(lat), longitude=f2i(lng), since_timestamp_ms=timestamp, cell_id=cellid ) self.last_time_map_object = time.time() return self.last_map_object
class PokemonGoBot(Datastore): @property def position(self): return self.api.actual_lat, self.api.actual_lng, self.api.actual_alt @property def noised_position(self): return self.api.noised_lat, self.api.noised_lng, self.api.noised_alt #@position.setter # these should be called through api now that gps replication is there... #def position(self, position_tuple): # self.api._position_lat, self.api._position_lng, self.api._position_alt = position_tuple @property def player_data(self): """ Returns the player data as received from the API. :return: The player data. :rtype: dict """ return self._player def __init__(self, config): # Database connection MUST be setup before migrations will work self.database = _init_database('/data/{}.db'.format(config.username)) self.config = config super(PokemonGoBot, self).__init__() self.fort_timeouts = dict() self.pokemon_list = json.load( open(os.path.join(_base_dir, 'data', 'pokemon.json')) ) self.item_list = json.load(open(os.path.join(_base_dir, 'data', 'items.json'))) # @var Metrics self.metrics = Metrics(self) self.latest_inventory = None self.cell = None self.recent_forts = [None] * config.forts_max_circle_size self.tick_count = 0 self.softban = False self.start_position = None self.last_map_object = None self.last_time_map_object = 0 self.logger = logging.getLogger(type(self).__name__) self.alt = self.config.gps_default_altitude # Make our own copy of the workers for this instance self.workers = [] # Theading setup for file writing self.web_update_queue = Queue.Queue(maxsize=1) self.web_update_thread = threading.Thread(target=self.update_web_location_worker) self.web_update_thread.start() # Heartbeat limiting self.heartbeat_threshold = self.config.heartbeat_threshold self.heartbeat_counter = 0 self.last_heartbeat = time.time() self.capture_locked = False # lock catching while moving to VIP pokemon client_id_file_path = os.path.join(_base_dir, 'data', 'mqtt_client_id') saved_info = shelve.open(client_id_file_path) key = 'client_id'.encode('utf-8') if key in saved_info: self.config.client_id = saved_info[key] else: self.config.client_id = str(uuid.uuid4()) saved_info[key] = self.config.client_id saved_info.close() def start(self): self._setup_event_system() self._setup_logging() self.sleep_schedule = SleepSchedule(self, self.config.sleep_schedule) if self.config.sleep_schedule else None if self.sleep_schedule: self.sleep_schedule.work() self._setup_api() self._load_recent_forts() init_inventory(self) self.display_player_info() self._print_character_info() if self.config.pokemon_bag_show_at_start and self.config.pokemon_bag_pokemon_info: self._print_list_pokemon() random.seed() def _setup_event_system(self): handlers = [] if self.config.logging and 'color' in self.config.logging and self.config.logging['color']: handlers.append(ColoredLoggingHandler(self)) else: handlers.append(LoggingHandler(self)) if self.config.enable_social: handlers.append(SocialHandler(self)) if self.config.websocket_server_url: if self.config.websocket_start_embedded_server: self.sio_runner = SocketIoRunner(self.config.websocket_server_url) self.sio_runner.start_listening_async() websocket_handler = SocketIoHandler( self, self.config.websocket_server_url ) handlers.append(websocket_handler) if self.config.websocket_remote_control: remote_control = WebsocketRemoteControl(self).start() # @var EventManager self.event_manager = EventManager(self.config.walker_limit_output, *handlers) self._register_events() if self.config.show_events: self.event_manager.event_report() sys.exit(1) # Registering event: # self.event_manager.register_event("location", parameters=['lat', 'lng']) # # Emitting event should be enough to add logging and send websocket # message: : # self.event_manager.emit('location', 'level'='info', data={'lat': 1, 'lng':1}), def _register_events(self): self.event_manager.register_event( 'location_found', parameters=('position', 'location') ) self.event_manager.register_event('api_error') self.event_manager.register_event('config_error') self.event_manager.register_event('login_started') self.event_manager.register_event('login_failed') self.event_manager.register_event('login_successful') self.event_manager.register_event('set_start_location') self.event_manager.register_event('load_cached_location') self.event_manager.register_event('location_cache_ignored') self.event_manager.register_event('debug') # ignore candy above threshold self.event_manager.register_event( 'ignore_candy_above_thresold', parameters=( 'name', 'amount', 'threshold' ) ) self.event_manager.register_event( 'position_update', parameters=( 'current_position', 'last_position', 'distance', # optional 'distance_unit' # optional ) ) self.event_manager.register_event( 'path_lap_update', parameters=( 'number_lap', 'number_lap_max' ) ) self.event_manager.register_event( 'path_lap_end', parameters=( 'duration', 'resume' ) ) self.event_manager.register_event('location_cache_error') self.event_manager.register_event('bot_start') self.event_manager.register_event('bot_exit') self.event_manager.register_event('bot_interrupted') # sleep stuff self.event_manager.register_event( 'next_sleep', parameters=( 'time', 'duration' ) ) self.event_manager.register_event( 'bot_sleep', parameters=( 'time_hms', 'wake' ) ) # random pause self.event_manager.register_event( 'next_random_pause', parameters=( 'time', 'duration' ) ) self.event_manager.register_event( 'bot_random_pause', parameters=( 'time_hms', 'resume' ) ) # recycle stuff self.event_manager.register_event( 'next_force_recycle', parameters=( 'time' ) ) self.event_manager.register_event('force_recycle') # random alive pause self.event_manager.register_event( 'next_random_alive_pause', parameters=( 'time', 'duration' ) ) self.event_manager.register_event( 'bot_random_alive_pause', parameters=( 'time_hms', 'resume' ) ) # fort stuff self.event_manager.register_event( 'spun_fort', parameters=( 'fort_id', 'latitude', 'longitude' ) ) self.event_manager.register_event( 'lured_pokemon_found', parameters=( 'fort_id', 'fort_name', 'encounter_id', 'latitude', 'longitude' ) ) self.event_manager.register_event( 'moving_to_fort', parameters=( 'fort_name', 'distance' ) ) self.event_manager.register_event( 'moving_to_lured_fort', parameters=( 'fort_name', 'distance', 'lure_distance' ) ) self.event_manager.register_event( 'spun_pokestop', parameters=( 'pokestop', 'exp', 'items' ) ) self.event_manager.register_event( 'pokestop_empty', parameters=('pokestop',) ) self.event_manager.register_event( 'pokestop_out_of_range', parameters=('pokestop',) ) self.event_manager.register_event( 'pokestop_on_cooldown', parameters=('pokestop', 'minutes_left') ) self.event_manager.register_event( 'unknown_spin_result', parameters=('status_code',) ) self.event_manager.register_event('pokestop_searching_too_often') self.event_manager.register_event('arrived_at_fort') # pokemon stuff self.event_manager.register_event( 'catchable_pokemon', parameters=( 'pokemon_id', 'spawn_point_id', 'encounter_id', 'latitude', 'longitude', 'expiration_timestamp_ms', 'pokemon_name' ) ) self.event_manager.register_event( 'pokemon_appeared', parameters=( 'pokemon', 'ncp', 'cp', 'iv', 'iv_display', 'encounter_id', 'latitude', 'longitude', 'pokemon_id' ) ) self.event_manager.register_event('no_pokeballs') self.event_manager.register_event('enough_ultraballs') self.event_manager.register_event( 'pokemon_catch_rate', parameters=( 'catch_rate', 'ball_name', 'berry_name', 'berry_count' ) ) self.event_manager.register_event( 'threw_berry', parameters=( 'berry_name', 'ball_name', 'new_catch_rate' ) ) self.event_manager.register_event( 'threw_pokeball', parameters=( 'throw_type', 'spin_label', 'ball_name', 'success_percentage', 'count_left' ) ) self.event_manager.register_event( 'pokemon_capture_failed', parameters=('pokemon',) ) self.event_manager.register_event( 'pokemon_vanished', parameters=( 'pokemon', 'encounter_id', 'latitude', 'longitude', 'pokemon_id' ) ) self.event_manager.register_event('pokemon_not_in_range') self.event_manager.register_event('pokemon_inventory_full') self.event_manager.register_event( 'pokemon_caught', parameters=( 'pokemon', 'ncp', 'cp', 'iv', 'iv_display', 'exp', 'encounter_id', 'latitude', 'longitude', 'pokemon_id', 'daily_catch_limit', 'caught_last_24_hour', ) ) self.event_manager.register_event( 'pokemon_evolved', parameters=('pokemon', 'iv', 'cp', 'xp', 'candy') ) self.event_manager.register_event( 'pokemon_upgraded', parameters=('pokemon', 'iv', 'cp', 'candy', 'stardust') ) self.event_manager.register_event('skip_evolve') self.event_manager.register_event('threw_berry_failed', parameters=('status_code',)) self.event_manager.register_event('vip_pokemon') self.event_manager.register_event('gained_candy', parameters=('quantity', 'type')) self.event_manager.register_event('catch_limit') self.event_manager.register_event('show_best_pokemon', parameters=('pokemons')) # level up stuff self.event_manager.register_event( 'level_up', parameters=( 'previous_level', 'current_level' ) ) self.event_manager.register_event( 'level_up_reward', parameters=('items',) ) # lucky egg self.event_manager.register_event( 'used_lucky_egg', parameters=('amount_left',) ) self.event_manager.register_event('lucky_egg_error') # softban self.event_manager.register_event('softban') self.event_manager.register_event('softban_fix') self.event_manager.register_event('softban_fix_done') # egg incubating self.event_manager.register_event( 'incubate_try', parameters=( 'incubator_id', 'egg_id' ) ) self.event_manager.register_event( 'incubate', parameters=('distance_in_km',) ) self.event_manager.register_event( 'next_egg_incubates', parameters=('eggs_left', 'eggs_inc', 'eggs') ) self.event_manager.register_event('incubator_already_used') self.event_manager.register_event('egg_already_incubating') self.event_manager.register_event( 'egg_hatched', parameters=( 'pokemon', 'cp', 'iv', 'exp', 'stardust', 'candy' ) ) # discard item self.event_manager.register_event( 'item_discarded', parameters=( 'amount', 'item', 'maximum' ) ) self.event_manager.register_event( 'item_discard_skipped', parameters=('space',) ) self.event_manager.register_event( 'item_discard_fail', parameters=('item',) ) # inventory self.event_manager.register_event('inventory_full') # release self.event_manager.register_event( 'keep_best_release', parameters=( 'amount', 'pokemon', 'criteria' ) ) self.event_manager.register_event( 'future_pokemon_release', parameters=( 'pokemon', 'cp', 'iv', 'below_iv', 'below_cp', 'cp_iv_logic' ) ) self.event_manager.register_event( 'pokemon_release', parameters=('pokemon', 'iv', 'cp', 'candy') ) # polyline walker self.event_manager.register_event( 'polyline_request', parameters=('url',) ) # cluster self.event_manager.register_event( 'found_cluster', parameters=( 'num_points', 'forts', 'radius', 'distance' ) ) self.event_manager.register_event( 'arrived_at_cluster', parameters=( 'num_points', 'forts', 'radius' ) ) # rename self.event_manager.register_event( 'rename_pokemon', parameters=('old_name', 'current_name',) ) self.event_manager.register_event( 'pokemon_nickname_invalid', parameters=('nickname',) ) self.event_manager.register_event( 'unset_pokemon_nickname', parameters=('old_name',) ) # Move To map pokemon self.event_manager.register_event( 'move_to_map_pokemon_fail', parameters=('message',) ) self.event_manager.register_event( 'move_to_map_pokemon_updated_map', parameters=('lat', 'lon') ) self.event_manager.register_event( 'move_to_map_pokemon_teleport_to', parameters=('poke_name', 'poke_dist', 'poke_lat', 'poke_lon', 'disappears_in') ) self.event_manager.register_event( 'move_to_map_pokemon_encounter', parameters=('poke_name', 'poke_dist', 'poke_lat', 'poke_lon', 'disappears_in') ) self.event_manager.register_event( 'move_to_map_pokemon_move_towards', parameters=('poke_name', 'poke_dist', 'poke_lat', 'poke_lon', 'disappears_in') ) self.event_manager.register_event( 'move_to_map_pokemon_teleport_back', parameters=('last_lat', 'last_lon') ) self.event_manager.register_event( 'moving_to_pokemon_throught_fort', parameters=('fort_name', 'distance','poke_name','poke_dist') ) self.event_manager.register_event( 'move_to_map_pokemon', parameters=('message') ) # cached recent_forts self.event_manager.register_event('loaded_cached_forts') self.event_manager.register_event('cached_fort') self.event_manager.register_event( 'no_cached_forts', parameters=('path', ) ) self.event_manager.register_event( 'error_caching_forts', parameters=('path', ) ) # database shit self.event_manager.register_event('catch_log') self.event_manager.register_event('evolve_log') self.event_manager.register_event('login_log') self.event_manager.register_event('transfer_log') self.event_manager.register_event('pokestop_log') self.event_manager.register_event('softban_log') self.event_manager.register_event( 'badges', parameters=('badge', 'level') ) self.event_manager.register_event( 'player_data', parameters=('player_data', ) ) def tick(self): self.health_record.heartbeat() self.cell = self.get_meta_cell() if self.sleep_schedule: self.sleep_schedule.work() now = time.time() * 1000 for fort in self.cell["forts"]: timeout = fort.get("cooldown_complete_timestamp_ms", 0) if timeout >= now: self.fort_timeouts[fort["id"]] = timeout self.tick_count += 1 # Check if session token has expired self.check_session(self.position) for worker in self.workers: if worker.work() == WorkerResult.RUNNING: return def get_meta_cell(self): location = self.position[0:2] cells = self.find_close_cells(*location) # Combine all cells into a single dict of the items we care about. forts = [] wild_pokemons = [] catchable_pokemons = [] for cell in cells: if "forts" in cell and len(cell["forts"]): forts += cell["forts"] if "wild_pokemons" in cell and len(cell["wild_pokemons"]): wild_pokemons += cell["wild_pokemons"] if "catchable_pokemons" in cell and len(cell["catchable_pokemons"]): catchable_pokemons += cell["catchable_pokemons"] # If there are forts present in the cells sent from the server or we don't yet have any cell data, return all data retrieved if len(forts) > 1 or not self.cell: return { "forts": forts, "wild_pokemons": wild_pokemons, "catchable_pokemons": catchable_pokemons } # If there are no forts present in the data from the server, keep our existing fort data and only update the pokemon cells. else: return { "forts": self.cell["forts"], "wild_pokemons": wild_pokemons, "catchable_pokemons": catchable_pokemons } def update_web_location(self, cells=[], lat=None, lng=None, alt=None): # we can call the function with no arguments and still get the position # and map_cells if lat is None: lat = self.api._position_lat if lng is None: lng = self.api._position_lng if alt is None: alt = self.api._position_alt # dont cache when teleport_to if self.api.teleporting: return if cells == []: location = self.position[0:2] cells = self.find_close_cells(*location) user_data_cells = os.path.join(_base_dir, 'data', 'cells-%s.json' % self.config.username) try: with open(user_data_cells, 'w') as outfile: json.dump(cells, outfile) except IOError as e: self.logger.info('[x] Error while opening location file: %s' % e) user_web_location = os.path.join( _base_dir, 'web', 'location-%s.json' % self.config.username ) # alt is unused atm but makes using *location easier try: with open(user_web_location, 'w') as outfile: json.dump({ 'lat': lat, 'lng': lng, 'alt': alt, 'cells': cells }, outfile) except IOError as e: self.logger.info('[x] Error while opening location file: %s' % e) user_data_lastlocation = os.path.join( _base_dir, 'data', 'last-location-%s.json' % self.config.username ) try: with open(user_data_lastlocation, 'w') as outfile: json.dump({'lat': lat, 'lng': lng, 'alt': alt, 'start_position': self.start_position}, outfile) except IOError as e: self.logger.info('[x] Error while opening location file: %s' % e) def find_close_cells(self, lat, lng): cellid = get_cell_ids(lat, lng) timestamp = [0, ] * len(cellid) response_dict = self.get_map_objects(lat, lng, timestamp, cellid) map_objects = response_dict.get( 'responses', {} ).get('GET_MAP_OBJECTS', {}) status = map_objects.get('status', None) map_cells = [] if status and status == 1: map_cells = map_objects['map_cells'] position = (lat, lng, 0) map_cells.sort( key=lambda x: distance( lat, lng, x['forts'][0]['latitude'], x['forts'][0]['longitude']) if x.get('forts', []) else 1e6 ) return map_cells def _setup_logging(self): log_level = logging.ERROR if self.config.debug: log_level = logging.DEBUG logging.getLogger("requests").setLevel(log_level) logging.getLogger("websocket").setLevel(log_level) logging.getLogger("socketio").setLevel(log_level) logging.getLogger("engineio").setLevel(log_level) logging.getLogger("socketIO-client").setLevel(log_level) logging.getLogger("pgoapi").setLevel(log_level) logging.getLogger("rpc_api").setLevel(log_level) if self.config.logging: logging_format = '%(message)s' logging_format_options = '' if ('show_log_level' not in self.config.logging) or self.config.logging['show_log_level']: logging_format = '[%(levelname)s] ' + logging_format if ('show_process_name' not in self.config.logging) or self.config.logging['show_process_name']: logging_format = '[%(name)10s] ' + logging_format if ('show_datetime' not in self.config.logging) or self.config.logging['show_datetime']: logging_format = '[%(asctime)s] ' + logging_format logging_format_options = '%Y-%m-%d %H:%M:%S' formatter = Formatter(logging_format,logging_format_options) for handler in logging.root.handlers[:]: handler.setFormatter(formatter) def check_session(self, position): # Check session expiry if self.api._auth_provider and self.api._auth_provider._ticket_expire: # prevent crash if return not numeric value if not self.is_numeric(self.api._auth_provider._ticket_expire): self.logger.info("Ticket expired value is not numeric", 'yellow') return remaining_time = \ self.api._auth_provider._ticket_expire / 1000 - time.time() if remaining_time < 60: self.event_manager.emit( 'api_error', sender=self, level='info', formatted='Session stale, re-logging in.' ) self.api = ApiWrapper(config=self.config) self.api.set_position(*position) self.login() self.api.activate_signature(self.get_encryption_lib()) @staticmethod def is_numeric(s): try: float(s) return True except ValueError: return False def login(self): self.event_manager.emit( 'login_started', sender=self, level='info', formatted="Login procedure started." ) lat, lng = self.position[0:2] self.api.set_position(lat, lng, self.alt) # or should the alt kept to zero? while not self.api.login( self.config.auth_service, str(self.config.username), str(self.config.password)): self.event_manager.emit( 'login_failed', sender=self, level='info', formatted="Login error, server busy. Waiting 10 seconds to try again." ) time.sleep(10) with self.database as conn: c = conn.cursor() c.execute("SELECT COUNT(name) FROM sqlite_master WHERE type='table' AND name='login'") result = c.fetchone() while True: if result[0] == 1: conn.execute('''INSERT INTO login (timestamp, message) VALUES (?, ?)''', (time.time(), 'LOGIN_SUCCESS')) break else: self.event_manager.emit( 'login_failed', sender=self, level='info', formatted="Login table not founded, skipping log" ) break self.event_manager.emit( 'login_successful', sender=self, level='info', formatted="Login successful." ) def get_encryption_lib(self): if _platform == "Windows" or _platform == "win32": # Check if we are on 32 or 64 bit if sys.maxsize > 2**32: file_name = 'encrypt_64.dll' else: file_name = 'encrypt.dll' else: file_name = 'encrypt.so' if self.config.encrypt_location == '': path = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir)) else: path = self.config.encrypt_location full_path = path + '/'+ file_name if not os.path.isfile(full_path): self.logger.error(file_name + ' is not found! Please place it in the bots root directory or set encrypt_location in config.') self.logger.info('Platform: '+ _platform + ' ' + file_name + ' directory: '+ path) sys.exit(1) else: self.logger.info('Found '+ file_name +'! Platform: ' + _platform + ' ' + file_name + ' directory: ' + path) return full_path def _setup_api(self): # instantiate pgoapi @var ApiWrapper self.api = ApiWrapper(config=self.config) # provide player position on the earth self._set_starting_position() self.login() # chain subrequests (methods) into one RPC call self.api.activate_signature(self.get_encryption_lib()) self.logger.info('') # send empty map_cells and then our position self.update_web_location() def _print_character_info(self): # get player profile call # ---------------------- response_dict = self.api.get_player() # print('Response dictionary: \n\r{}'.format(json.dumps(response_dict, indent=2))) currency_1 = "0" currency_2 = "0" if response_dict: self._player = response_dict['responses']['GET_PLAYER']['player_data'] player = self._player else: self.logger.info( "The API didn't return player info, servers are unstable - " "retrying.", 'red' ) sleep(5) self._print_character_info() # @@@ TODO: Convert this to d/m/Y H:M:S creation_date = datetime.datetime.fromtimestamp( player['creation_timestamp_ms'] / 1e3) creation_date = creation_date.strftime("%Y/%m/%d %H:%M:%S") pokecoins = '0' stardust = '0' items_inventory = inventory.items() if 'amount' in player['currencies'][0]: pokecoins = player['currencies'][0]['amount'] if 'amount' in player['currencies'][1]: stardust = player['currencies'][1]['amount'] self.logger.info('') self.logger.info('--- {username} ---'.format(**player)) self.logger.info( 'Pokemon Bag: {}/{}'.format( inventory.Pokemons.get_space_used(), inventory.get_pokemon_inventory_size() ) ) self.logger.info( 'Items: {}/{}'.format( inventory.Items.get_space_used(), inventory.get_item_inventory_size() ) ) self.logger.info( 'Stardust: {}'.format(stardust) + ' | Pokecoins: {}'.format(pokecoins) ) # Items Output self.logger.info( 'PokeBalls: ' + str(items_inventory.get(1).count) + ' | GreatBalls: ' + str(items_inventory.get(2).count) + ' | UltraBalls: ' + str(items_inventory.get(3).count) + ' | MasterBalls: ' + str(items_inventory.get(4).count)) self.logger.info( 'RazzBerries: ' + str(items_inventory.get(701).count) + ' | BlukBerries: ' + str(items_inventory.get(702).count) + ' | NanabBerries: ' + str(items_inventory.get(703).count)) self.logger.info( 'LuckyEgg: ' + str(items_inventory.get(301).count) + ' | Incubator: ' + str(items_inventory.get(902).count) + ' | TroyDisk: ' + str(items_inventory.get(501).count)) self.logger.info( 'Potion: ' + str(items_inventory.get(101).count) + ' | SuperPotion: ' + str(items_inventory.get(102).count) + ' | HyperPotion: ' + str(items_inventory.get(103).count) + ' | MaxPotion: ' + str(items_inventory.get(104).count)) self.logger.info( 'Incense: ' + str(items_inventory.get(401).count) + ' | IncenseSpicy: ' + str(items_inventory.get(402).count) + ' | IncenseCool: ' + str(items_inventory.get(403).count)) self.logger.info( 'Revive: ' + str(items_inventory.get(201).count) + ' | MaxRevive: ' + str(items_inventory.get(202).count)) self.logger.info('') def _print_list_pokemon(self): # get pokemon list bag = inventory.pokemons().all() id_list =list(set(map(lambda x: x.pokemon_id, bag))) id_list.sort() pokemon_list = [filter(lambda x: x.pokemon_id == y, bag) for y in id_list] show_count = self.config.pokemon_bag_show_count show_candies = self.config.pokemon_bag_show_candies poke_info_displayed = self.config.pokemon_bag_pokemon_info def get_poke_info(info, pokemon): poke_info = { 'cp': 'CP {}'.format(pokemon.cp), 'iv_ads': 'A/D/S {}/{}/{}'.format(pokemon.iv_attack, pokemon.iv_defense, pokemon.iv_stamina), 'iv_pct': 'IV {}'.format(pokemon.iv), 'ivcp': 'IVCP {}'.format(round(pokemon.ivcp,2)), 'ncp': 'NCP {}'.format(round(pokemon.cp_percent,2)), 'level': "Level {}".format(pokemon.level), 'hp': 'HP {}/{}'.format(pokemon.hp, pokemon.hp_max), 'moveset': 'Moves: {}'.format(pokemon.moveset), 'dps': 'DPS {}'.format(round(pokemon.moveset.dps, 2)) } if info not in poke_info: raise ConfigException("info '{}' isn't available for displaying".format(info)) return poke_info[info] self.logger.info('Pokemon:') for pokes in pokemon_list: line_p = '#{} {}'.format(pokes[0].pokemon_id, pokes[0].name) if show_count: line_p += '[{}]'.format(len(pokes)) if show_candies: line_p += '[{} candies]'.format(pokes[0].candy_quantity) line_p += ': ' poke_info = ['({})'.format(', '.join([get_poke_info(x, p) for x in poke_info_displayed])) for p in pokes] self.logger.info(line_p + ' | '.join(poke_info)) self.logger.info('') def use_lucky_egg(self): return self.api.use_item_xp_boost(item_id=301) def _set_starting_position(self): self.event_manager.emit( 'set_start_location', sender=self, level='info', formatted='Setting start location.' ) has_position = False if self.config.test: # TODO: Add unit tests return if self.config.location: location_str = self.config.location location = self.get_pos_by_name(location_str.replace(" ", "")) msg = "Location found: {location} {position}" self.event_manager.emit( 'location_found', sender=self, level='info', formatted=msg, data={ 'location': location_str, 'position': location } ) self.api.set_position(*location) self.event_manager.emit( 'position_update', sender=self, level='info', formatted="Now at {current_position}", data={ 'current_position': self.position, 'last_position': '', 'distance': '', 'distance_unit': '' } ) self.start_position = self.position has_position = True if self.config.location_cache: try: # save location flag used to pull the last known location from # the location.json self.event_manager.emit( 'load_cached_location', sender=self, level='debug', formatted='Loading cached location...' ) json_file = os.path.join(_base_dir, 'data', 'last-location-%s.json' % self.config.username) try: with open(json_file, "r") as infile: location_json = json.load(infile) except (IOError, ValueError): # Unable to read json file. # File may be corrupt. Create a new one. location_json = [] except: raise FileIOException("Unexpected error reading from {}".web_inventory) location = ( location_json['lat'], location_json['lng'], location_json['alt'], ) # If location has been set in config, only use cache if starting position has not differed if has_position and 'start_position' in location_json: last_start_position = tuple(location_json.get('start_position', [])) # Start position has to have been set on a previous run to do this check if last_start_position and last_start_position != self.start_position: msg = 'Going to a new place, ignoring cached location.' self.event_manager.emit( 'location_cache_ignored', sender=self, level='debug', formatted=msg ) return self.api.set_position(*location) self.event_manager.emit( 'position_update', sender=self, level='debug', formatted='Loaded location {current_position} from cache', data={ 'current_position': location, 'last_position': '', 'distance': '', 'distance_unit': '' } ) has_position = True except Exception: if has_position is False: sys.exit( "No cached Location. Please specify initial location." ) self.event_manager.emit( 'location_cache_error', sender=self, level='debug', formatted='Parsing cached location failed.' ) def get_pos_by_name(self, location_name): # Check if given location name, belongs to favorite_locations favorite_location_coords = self._get_pos_by_fav_location(location_name) if favorite_location_coords is not None: return favorite_location_coords # Check if the given location is already a coordinate. if ',' in location_name: possible_coordinates = re.findall( "[-]?\d{1,3}[.]\d{3,7}", location_name ) if len(possible_coordinates) >= 2: # 2 matches, this must be a coordinate. We'll bypass the Google # geocode so we keep the exact location. self.logger.info( '[x] Coordinates found in passed in location, ' 'not geocoding.' ) return float(possible_coordinates[0]), float(possible_coordinates[1]), (float(possible_coordinates[2]) if len(possible_coordinates) == 3 else self.alt) geolocator = GoogleV3(api_key=self.config.gmapkey) loc = geolocator.geocode(location_name, timeout=10) return float(loc.latitude), float(loc.longitude), float(loc.altitude) def _get_pos_by_fav_location(self, location_name): location_name = location_name.lower() coords = None for location in self.config.favorite_locations: if location.get('name').lower() == location_name: coords = re.findall( "[-]?\d{1,3}[.]\d{3,7}", location.get('coords').strip() ) if len(coords) >= 2: self.logger.info('Favorite location found: {} ({})'.format(location_name, coords)) break #TODO: This is real bad if coords is None: return coords else: return float(coords[0]), float(coords[1]), (float(coords[2]) if len(coords) == 3 else self.alt) def heartbeat(self): # Remove forts that we can now spin again. now = time.time() self.fort_timeouts = {id: timeout for id, timeout in self.fort_timeouts.iteritems() if timeout >= now * 1000} if now - self.last_heartbeat >= self.heartbeat_threshold: self.last_heartbeat = now request = self.api.create_request() request.get_player() request.check_awarded_badges() responses = request.call() if responses['responses']['GET_PLAYER']['success'] == True: # we get the player_data anyway, might as well store it self._player = responses['responses']['GET_PLAYER']['player_data'] self.event_manager.emit( 'player_data', sender=self, level='debug', formatted='player_data: {player_data}', data={'player_data': self._player} ) if responses['responses']['CHECK_AWARDED_BADGES']['success'] == True: # store awarded_badges reponse to be used in a task or part of heartbeat self._awarded_badges = responses['responses']['CHECK_AWARDED_BADGES'] if self._awarded_badges.has_key('awarded_badges'): i = 0 for badge in self._awarded_badges['awarded_badges']: badgelevel = self._awarded_badges['awarded_badge_levels'][i] badgename = BadgeType_pb2._BADGETYPE.values_by_number[badge].name i += 1 self.event_manager.emit( 'badges', sender=self, level='info', formatted='awarded badge: {badge}, lvl {level}', data={'badge': badgename, 'level': badgelevel} ) human_behaviour.action_delay(3, 10) inventory.refresh_inventory() try: self.web_update_queue.put_nowait(True) # do this outside of thread every tick except Queue.Full: pass def update_web_location_worker(self): while True: self.web_update_queue.get() self.update_web_location() def display_player_info(self): player_stats = player() if player_stats: nextlvlxp = (int(player_stats.next_level_xp) - int(player_stats.exp)) self.logger.info( 'Level: {}'.format(player_stats.level) + ' (Next Level: {} XP)'.format(nextlvlxp) + ' (Total: {} XP)' ''.format(player_stats.exp)) self.logger.info( 'Pokemon Captured: ' '{}'.format(player_stats.pokemons_captured) + ' | Pokestops Visited: ' '{}'.format(player_stats.poke_stop_visits)) def get_forts(self, order_by_distance=False): forts = [fort for fort in self.cell['forts'] if 'latitude' in fort and 'type' in fort] if order_by_distance: forts.sort(key=lambda x: distance( self.position[0], self.position[1], x['latitude'], x['longitude'] )) return forts def get_map_objects(self, lat, lng, timestamp, cellid): if time.time() - self.last_time_map_object < self.config.map_object_cache_time: return self.last_map_object self.last_map_object = self.api.get_map_objects( latitude=f2i(lat), longitude=f2i(lng), since_timestamp_ms=timestamp, cell_id=cellid ) self.last_time_map_object = time.time() return self.last_map_object def _load_recent_forts(self): if not self.config.forts_cache_recent_forts: return cached_forts_path = os.path.join(_base_dir, 'data', 'recent-forts-%s.json' % self.config.username) try: # load the cached recent forts cached_recent_forts = [] try: with open(cached_forts_path) as f: cached_recent_forts = json.load(f) except (IOError, ValueError) as e: self.logger.info('[x] Error while opening cached forts: %s' % e, 'red') pass except: raise FileIOException("Unexpected error opening {}".cached_forts_path) num_cached_recent_forts = len(cached_recent_forts) num_recent_forts = len(self.recent_forts) # Handles changes in max_circle_size if not num_recent_forts: self.recent_forts = [] elif num_recent_forts > num_cached_recent_forts: self.recent_forts[-num_cached_recent_forts:] = cached_recent_forts elif num_recent_forts < num_cached_recent_forts: self.recent_forts = cached_recent_forts[-num_recent_forts:] else: self.recent_forts = cached_recent_forts self.event_manager.emit( 'loaded_cached_forts', sender=self, level='debug', formatted='Loaded cached forts...' ) except IOError: self.event_manager.emit( 'no_cached_forts', sender=self, level='debug', formatted='Starting new cached forts for {path}', data={'path': cached_forts_path} )
class PokemonGoBot(object): @property def position(self): return self.api._position_lat, self.api._position_lng, 0 @position.setter def position(self, position_tuple): self.api._position_lat, self.api._position_lng, self.api._position_alt = position_tuple def __init__(self, config): self.config = config self.fort_timeouts = dict() self.pokemon_list = json.load( open(os.path.join('data', 'pokemon.json')) ) self.item_list = json.load(open(os.path.join('data', 'items.json'))) self.metrics = Metrics(self) self.latest_inventory = None self.cell = None self.recent_forts = [None] * config.forts_max_circle_size self.tick_count = 0 self.softban = False self.start_position = None self.last_map_object = None self.last_time_map_object = 0 # Make our own copy of the workers for this instance self.workers = [] def start(self): self._setup_logging() self._setup_api() random.seed() def _setup_event_system(self): handlers = [LoggingHandler()] if self.config.websocket_server: websocket_handler = SocketIoHandler(self.config.websocket_server_url) handlers.append(websocket_handler) if self.config.websocket_start_embedded_server: self.sio_runner = SocketIoRunner(self.config.websocket_server_url) self.sio_runner.start_listening_async() self.event_manager = EventManager(*handlers) # Registering event: # self.event_manager.register_event("location", parameters=['lat', 'lng']) # # Emitting event should be enough to add logging and send websocket # message: : # self.event_manager.emit('location', 'level'='info', data={'lat': 1, 'lng':1}), def tick(self): self.cell = self.get_meta_cell() self.tick_count += 1 # Check if session token has expired self.check_session(self.position[0:2]) for worker in self.workers: if worker.work() == WorkerResult.RUNNING: return def get_meta_cell(self): location = self.position[0:2] cells = self.find_close_cells(*location) # Combine all cells into a single dict of the items we care about. forts = [] wild_pokemons = [] catchable_pokemons = [] for cell in cells: if "forts" in cell and len(cell["forts"]): forts += cell["forts"] if "wild_pokemons" in cell and len(cell["wild_pokemons"]): wild_pokemons += cell["wild_pokemons"] if "catchable_pokemons" in cell and len(cell["catchable_pokemons"]): catchable_pokemons += cell["catchable_pokemons"] # If there are forts present in the cells sent from the server or we don't yet have any cell data, return all data retrieved if len(forts) > 1 or not self.cell: return { "forts": forts, "wild_pokemons": wild_pokemons, "catchable_pokemons": catchable_pokemons } # If there are no forts present in the data from the server, keep our existing fort data and only update the pokemon cells. else: return { "forts": self.cell["forts"], "wild_pokemons": wild_pokemons, "catchable_pokemons": catchable_pokemons } def update_web_location(self, cells=[], lat=None, lng=None, alt=None): # we can call the function with no arguments and still get the position # and map_cells if lat is None: lat = self.api._position_lat if lng is None: lng = self.api._position_lng if alt is None: alt = 0 if cells == []: location = self.position[0:2] cells = self.find_close_cells(*location) # insert detail info about gym to fort for cell in cells: if 'forts' in cell: for fort in cell['forts']: if fort.get('type') != 1: response_gym_details = self.api.get_gym_details( gym_id=fort.get('id'), player_latitude=lng, player_longitude=lat, gym_latitude=fort.get('latitude'), gym_longitude=fort.get('longitude') ) fort['gym_details'] = response_gym_details.get( 'responses', {} ).get('GET_GYM_DETAILS', None) user_data_cells = "data/cells-%s.json" % self.config.username with open(user_data_cells, 'w') as outfile: json.dump(cells, outfile) user_web_location = os.path.join( 'web', 'location-%s.json' % self.config.username ) # alt is unused atm but makes using *location easier try: with open(user_web_location, 'w') as outfile: json.dump({ 'lat': lat, 'lng': lng, 'alt': alt, 'cells': cells }, outfile) except IOError as e: logger.log('[x] Error while opening location file: %s' % e, 'red') user_data_lastlocation = os.path.join( 'data', 'last-location-%s.json' % self.config.username ) try: with open(user_data_lastlocation, 'w') as outfile: json.dump({'lat': lat, 'lng': lng, 'start_position': self.start_position}, outfile) except IOError as e: logger.log('[x] Error while opening location file: %s' % e, 'red') def find_close_cells(self, lat, lng): cellid = get_cell_ids(lat, lng) timestamp = [0, ] * len(cellid) response_dict = self.get_map_objects(lat, lng, timestamp, cellid) map_objects = response_dict.get( 'responses', {} ).get('GET_MAP_OBJECTS', {}) status = map_objects.get('status', None) map_cells = [] if status and status == 1: map_cells = map_objects['map_cells'] position = (lat, lng, 0) map_cells.sort( key=lambda x: distance( lat, lng, x['forts'][0]['latitude'], x['forts'][0]['longitude']) if x.get('forts', []) else 1e6 ) return map_cells def _setup_logging(self): self.log = logging.getLogger(__name__) # log settings # log format logging.basicConfig( level=logging.DEBUG, format='%(asctime)s [%(name)10s] [%(levelname)5s] %(message)s') if self.config.debug: logging.getLogger("requests").setLevel(logging.DEBUG) logging.getLogger("websocket").setLevel(logging.DEBUG) logging.getLogger("socketio").setLevel(logging.DEBUG) logging.getLogger("engineio").setLevel(logging.DEBUG) logging.getLogger("socketIO-client").setLevel(logging.DEBUG) logging.getLogger("pgoapi").setLevel(logging.DEBUG) logging.getLogger("rpc_api").setLevel(logging.DEBUG) else: logging.getLogger("requests").setLevel(logging.ERROR) logging.getLogger("websocket").setLevel(logging.ERROR) logging.getLogger("socketio").setLevel(logging.ERROR) logging.getLogger("engineio").setLevel(logging.ERROR) logging.getLogger("socketIO-client").setLevel(logging.ERROR) logging.getLogger("pgoapi").setLevel(logging.ERROR) logging.getLogger("rpc_api").setLevel(logging.ERROR) def check_session(self, position): # Check session expiry if self.api._auth_provider and self.api._auth_provider._ticket_expire: # prevent crash if return not numeric value if not self.is_numeric(self.api._auth_provider._ticket_expire): logger.log("Ticket expired value is not numeric", 'yellow') return remaining_time = \ self.api._auth_provider._ticket_expire / 1000 - time.time() if remaining_time < 60: logger.log("Session stale, re-logging in", 'yellow') position = self.position self.api = ApiWrapper() self.position = position self.login() @staticmethod def is_numeric(s): try: float(s) return True except ValueError: return False def login(self): logger.log('Attempting login to Pokemon Go.', 'white') lat, lng = self.position[0:2] self.api.set_position(lat, lng, 0) while not self.api.login( self.config.auth_service, str(self.config.username), str(self.config.password)): logger.log('[X] Login Error, server busy', 'red') logger.log('[X] Waiting 10 seconds to try again', 'red') time.sleep(10) logger.log('Login to Pokemon Go successful.', 'green') def _setup_api(self): # instantiate pgoapi self.api = ApiWrapper() # provide player position on the earth self._set_starting_position() self.login() # chain subrequests (methods) into one RPC call self._print_character_info() logger.log('') self.update_inventory() # send empty map_cells and then our position self.update_web_location() def _print_character_info(self): # get player profile call # ---------------------- response_dict = self.api.get_player() # print('Response dictionary: \n\r{}'.format(json.dumps(response_dict, indent=2))) currency_1 = "0" currency_2 = "0" if response_dict: self._player = response_dict['responses']['GET_PLAYER']['player_data'] player = self._player else: logger.log( "The API didn't return player info, servers are unstable - " "retrying.", 'red' ) sleep(5) self._print_character_info() # @@@ TODO: Convert this to d/m/Y H:M:S creation_date = datetime.datetime.fromtimestamp( player['creation_timestamp_ms'] / 1e3) creation_date = creation_date.strftime("%Y/%m/%d %H:%M:%S") pokecoins = '0' stardust = '0' items_stock = self.current_inventory() if 'amount' in player['currencies'][0]: pokecoins = player['currencies'][0]['amount'] if 'amount' in player['currencies'][1]: stardust = player['currencies'][1]['amount'] logger.log('') logger.log('--- {username} ---'.format(**player), 'cyan') self.get_player_info() logger.log( 'Pokemon Bag: {}/{}'.format( self.get_inventory_count('pokemon'), player['max_pokemon_storage'] ), 'cyan' ) logger.log( 'Items: {}/{}'.format( self.get_inventory_count('item'), player['max_item_storage'] ), 'cyan' ) logger.log( 'Stardust: {}'.format(stardust) + ' | Pokecoins: {}'.format(pokecoins), 'cyan' ) # Items Output logger.log( 'PokeBalls: ' + str(items_stock[1]) + ' | GreatBalls: ' + str(items_stock[2]) + ' | UltraBalls: ' + str(items_stock[3]), 'cyan') logger.log( 'RazzBerries: ' + str(items_stock[701]) + ' | BlukBerries: ' + str(items_stock[702]) + ' | NanabBerries: ' + str(items_stock[703]), 'cyan') logger.log( 'LuckyEgg: ' + str(items_stock[301]) + ' | Incubator: ' + str(items_stock[902]) + ' | TroyDisk: ' + str(items_stock[501]), 'cyan') logger.log( 'Potion: ' + str(items_stock[101]) + ' | SuperPotion: ' + str(items_stock[102]) + ' | HyperPotion: ' + str(items_stock[103]), 'cyan') logger.log( 'Incense: ' + str(items_stock[401]) + ' | IncenseSpicy: ' + str(items_stock[402]) + ' | IncenseCool: ' + str(items_stock[403]), 'cyan') logger.log( 'Revive: ' + str(items_stock[201]) + ' | MaxRevive: ' + str(items_stock[202]), 'cyan') logger.log('') def use_lucky_egg(self): return self.api.use_item_xp_boost(item_id=301) def get_inventory(self): if self.latest_inventory is None: self.latest_inventory = self.api.get_inventory() return self.latest_inventory def update_inventory(self): response = self.get_inventory() self.inventory = list() inventory_items = response.get('responses', {}).get('GET_INVENTORY', {}).get( 'inventory_delta', {}).get('inventory_items', {}) if inventory_items: for item in inventory_items: item_info = item.get('inventory_item_data', {}).get('item', {}) if {"item_id", "count"}.issubset(set(item_info.keys())): self.inventory.append(item['inventory_item_data']['item']) def current_inventory(self): inventory_req = self.get_inventory() inventory_dict = inventory_req['responses']['GET_INVENTORY'][ 'inventory_delta']['inventory_items'] user_web_inventory = 'web/inventory-%s.json' % self.config.username with open(user_web_inventory, 'w') as outfile: json.dump(inventory_dict, outfile) # get player items stock # ---------------------- items_stock = {x.value: 0 for x in list(Item)} for item in inventory_dict: item_dict = item.get('inventory_item_data', {}).get('item', {}) item_count = item_dict.get('count') item_id = item_dict.get('item_id') if item_count and item_id: if item_id in items_stock: items_stock[item_id] = item_count return items_stock def item_inventory_count(self, id): inventory_req = self.get_inventory() inventory_dict = inventory_req['responses'][ 'GET_INVENTORY']['inventory_delta']['inventory_items'] if id == 'all': return self._all_items_inventory_count(inventory_dict) else: return self._item_inventory_count_per_id(id, inventory_dict) def _item_inventory_count_per_id(self, id, inventory_dict): item_count = 0 for item in inventory_dict: item_dict = item.get('inventory_item_data', {}).get('item', {}) item_id = item_dict.get('item_id', False) item_count = item_dict.get('count', False) if item_id == int(id) and item_count: return item_count return 0 def _all_items_inventory_count(self, inventory_dict): item_count_dict = {} for item in inventory_dict: item_dict = item.get('inventory_item_data', {}).get('item', {}) item_id = item_dict.get('item_id', False) item_count = item_dict.get('count', False) if item_id and item_count: item_count_dict[item_id] = item_count return item_count_dict def _set_starting_position(self): has_position = False if self.config.test: # TODO: Add unit tests return if self.config.location: location_str = self.config.location.encode('utf-8') location = (self.get_pos_by_name(location_str.replace(" ", ""))) self.api.set_position(*location) self.start_position = self.position logger.log('') logger.log('Location Found: {}'.format(location_str)) logger.log('GeoPosition: {}'.format(self.position)) logger.log('') has_position = True if self.config.location_cache: try: # save location flag used to pull the last known location from # the location.json logger.log('[x] Parsing cached location...') with open('data/last-location-%s.json' % self.config.username) as f: location_json = json.load(f) location = ( location_json['lat'], location_json['lng'], 0.0 ) # If location has been set in config, only use cache if starting position has not differed if has_position and 'start_position' in location_json: last_start_position = tuple(location_json.get('start_position', [])) # Start position has to have been set on a previous run to do this check if last_start_position and last_start_position != self.start_position: logger.log('[x] Last location flag used but with a stale starting location', 'yellow') logger.log('[x] Using new starting location, {}'.format(self.position)) return self.api.set_position(*location) logger.log('') logger.log( '[x] Last location flag used. Overriding passed in location' ) logger.log( '[x] Last in-game location was set as: {}'.format( self.position ) ) logger.log('') has_position = True except Exception: if has_position is False: sys.exit( "No cached Location. Please specify initial location." ) logger.log( '[x] Parsing cached location failed, try to use the ' 'initial location...' ) def get_pos_by_name(self, location_name): # Check if the given location is already a coordinate. if ',' in location_name: possible_coordinates = re.findall( "[-]?\d{1,3}[.]\d{3,7}", location_name ) if len(possible_coordinates) == 2: # 2 matches, this must be a coordinate. We'll bypass the Google # geocode so we keep the exact location. logger.log( '[x] Coordinates found in passed in location, ' 'not geocoding.' ) return float(possible_coordinates[0]), float(possible_coordinates[1]), float("0.0") geolocator = GoogleV3(api_key=self.config.gmapkey) loc = geolocator.geocode(location_name, timeout=10) return float(loc.latitude), float(loc.longitude), float(loc.altitude) def heartbeat(self): # Remove forts that we can now spin again. self.fort_timeouts = {id: timeout for id, timeout in self.fort_timeouts.iteritems() if timeout >= time.time() * 1000} request = self.api.create_request() request.get_player() request.check_awarded_badges() request.call() self.update_web_location() # updates every tick def get_inventory_count(self, what): response_dict = self.get_inventory() inventory_items = response_dict.get('responses', {}).get('GET_INVENTORY', {}).get( 'inventory_delta', {}).get('inventory_items', {}) if inventory_items: pokecount = 0 itemcount = 1 for item in inventory_items: if 'inventory_item_data' in item: if 'pokemon_data' in item['inventory_item_data']: pokecount += 1 itemcount += item['inventory_item_data'].get('item', {}).get('count', 0) if 'pokemon' in what: return pokecount if 'item' in what: return itemcount return '0' def get_player_info(self): response_dict = self.get_inventory() inventory_items = response_dict.get('responses', {}).get('GET_INVENTORY', {}).get( 'inventory_delta', {}).get('inventory_items', {}) if inventory_items: pokecount = 0 itemcount = 1 for item in inventory_items: # print('item {}'.format(item)) playerdata = item.get('inventory_item_data', {}).get('player_stats') if playerdata: nextlvlxp = (int(playerdata.get('next_level_xp', 0)) - int(playerdata.get('experience', 0))) if 'level' in playerdata and 'experience' in playerdata: logger.log( 'Level: {level}'.format( **playerdata) + ' (Next Level: {} XP)'.format( nextlvlxp) + ' (Total: {experience} XP)' ''.format(**playerdata), 'cyan') if 'pokemons_captured' in playerdata and 'poke_stop_visits' in playerdata: logger.log( 'Pokemon Captured: ' '{pokemons_captured}'.format( **playerdata) + ' | Pokestops Visited: ' '{poke_stop_visits}'.format( **playerdata), 'cyan') def has_space_for_loot(self): number_of_things_gained_by_stop = 5 enough_space = ( self.get_inventory_count('item') < self._player['max_item_storage'] - number_of_things_gained_by_stop ) return enough_space def get_forts(self, order_by_distance=False): forts = [fort for fort in self.cell['forts'] if 'latitude' in fort and 'type' in fort] if order_by_distance: forts.sort(key=lambda x: distance( self.position[0], self.position[1], x['latitude'], x['longitude'] )) return forts def get_map_objects(self, lat, lng, timestamp, cellid): if time.time() - self.last_time_map_object < self.config.map_object_cache_time: return self.last_map_object self.last_map_object = self.api.get_map_objects( latitude=f2i(lat), longitude=f2i(lng), since_timestamp_ms=timestamp, cell_id=cellid ) self.last_time_map_object = time.time() return self.last_map_object
class PokemonGoBot(Datastore): @property def position(self): return self.api.actual_lat, self.api.actual_lng, self.api.actual_alt @property def noised_position(self): return self.api.noised_lat, self.api.noised_lng, self.api.noised_alt #@position.setter # these should be called through api now that gps replication is there... #def position(self, position_tuple): # self.api._position_lat, self.api._position_lng, self.api._position_alt = position_tuple @property def player_data(self): """ Returns the player data as received from the API. :return: The player data. :rtype: dict """ return self._player def __init__(self, config): # Database connection MUST be setup before migrations will work self.database = _init_database('/data/{}.db'.format(config.username)) self.config = config super(PokemonGoBot, self).__init__() self.fort_timeouts = dict() self.pokemon_list = json.load( open(os.path.join(_base_dir, 'data', 'pokemon.json')) ) self.item_list = json.load(open(os.path.join(_base_dir, 'data', 'items.json'))) self.metrics = Metrics(self) self.latest_inventory = None self.cell = None self.recent_forts = [None] * config.forts_max_circle_size self.tick_count = 0 self.softban = False self.start_position = None self.last_map_object = None self.last_time_map_object = 0 self.logger = logging.getLogger(type(self).__name__) self.alt = self.config.gps_default_altitude # Make our own copy of the workers for this instance self.workers = [] # Theading setup for file writing self.web_update_queue = Queue.Queue(maxsize=1) self.web_update_thread = threading.Thread(target=self.update_web_location_worker) self.web_update_thread.start() # Heartbeat limiting self.heartbeat_threshold = self.config.heartbeat_threshold self.heartbeat_counter = 0 self.last_heartbeat = time.time() def start(self): self._setup_event_system() self._setup_logging() self._setup_api() self._load_recent_forts() init_inventory(self) self.display_player_info() self._print_character_info() if self.config.pokemon_bag_show_at_start and self.config.pokemon_bag_pokemon_info: self._print_list_pokemon() random.seed() def _setup_event_system(self): handlers = [] if self.config.logging_color: handlers.append(ColoredLoggingHandler()) else: handlers.append(LoggingHandler()) if self.config.enable_social: handlers.append(SocialHandler(self)) if self.config.websocket_server_url: if self.config.websocket_start_embedded_server: self.sio_runner = SocketIoRunner(self.config.websocket_server_url) self.sio_runner.start_listening_async() websocket_handler = SocketIoHandler( self, self.config.websocket_server_url ) handlers.append(websocket_handler) if self.config.websocket_remote_control: remote_control = WebsocketRemoteControl(self).start() self.event_manager = EventManager(*handlers) self._register_events() if self.config.show_events: self.event_manager.event_report() sys.exit(1) # Registering event: # self.event_manager.register_event("location", parameters=['lat', 'lng']) # # Emitting event should be enough to add logging and send websocket # message: : # self.event_manager.emit('location', 'level'='info', data={'lat': 1, 'lng':1}), def _register_events(self): self.event_manager.register_event( 'location_found', parameters=('position', 'location') ) self.event_manager.register_event('api_error') self.event_manager.register_event('config_error') self.event_manager.register_event('login_started') self.event_manager.register_event('login_failed') self.event_manager.register_event('login_successful') self.event_manager.register_event('set_start_location') self.event_manager.register_event('load_cached_location') self.event_manager.register_event('location_cache_ignored') self.event_manager.register_event( 'position_update', parameters=( 'current_position', 'last_position', 'distance', # optional 'distance_unit' # optional ) ) self.event_manager.register_event( 'path_lap_update', parameters=( 'number_lap', 'number_lap_max' ) ) self.event_manager.register_event( 'path_lap_end', parameters=( 'duration', 'resume' ) ) self.event_manager.register_event('location_cache_error') self.event_manager.register_event('bot_start') self.event_manager.register_event('bot_exit') self.event_manager.register_event('bot_interrupted') # sleep stuff self.event_manager.register_event( 'next_sleep', parameters=('time',) ) self.event_manager.register_event( 'bot_sleep', parameters=( 'time_hms', 'wake' ) ) # random pause self.event_manager.register_event( 'next_random_pause', parameters=( 'time', 'duration' ) ) self.event_manager.register_event( 'bot_random_pause', parameters=( 'time_hms', 'resume' ) ) # random alive pause self.event_manager.register_event( 'next_random_alive_pause', parameters=( 'time', 'duration' ) ) self.event_manager.register_event( 'bot_random_alive_pause', parameters=( 'time_hms', 'resume' ) ) # fort stuff self.event_manager.register_event( 'spun_fort', parameters=( 'fort_id', 'latitude', 'longitude' ) ) self.event_manager.register_event( 'lured_pokemon_found', parameters=( 'fort_id', 'fort_name', 'encounter_id', 'latitude', 'longitude' ) ) self.event_manager.register_event( 'moving_to_fort', parameters=( 'fort_name', 'distance' ) ) self.event_manager.register_event( 'moving_to_lured_fort', parameters=( 'fort_name', 'distance', 'lure_distance' ) ) self.event_manager.register_event( 'spun_pokestop', parameters=( 'pokestop', 'exp', 'items' ) ) self.event_manager.register_event( 'pokestop_empty', parameters=('pokestop',) ) self.event_manager.register_event( 'pokestop_out_of_range', parameters=('pokestop',) ) self.event_manager.register_event( 'pokestop_on_cooldown', parameters=('pokestop', 'minutes_left') ) self.event_manager.register_event( 'unknown_spin_result', parameters=('status_code',) ) self.event_manager.register_event('pokestop_searching_too_often') self.event_manager.register_event('arrived_at_fort') # pokemon stuff self.event_manager.register_event( 'catchable_pokemon', parameters=( 'pokemon_id', 'spawn_point_id', 'encounter_id', 'latitude', 'longitude', 'expiration_timestamp_ms', 'pokemon_name' ) ) self.event_manager.register_event( 'pokemon_appeared', parameters=( 'pokemon', 'ncp', 'cp', 'iv', 'iv_display', 'encounter_id', 'latitude', 'longitude', 'pokemon_id' ) ) self.event_manager.register_event('no_pokeballs') self.event_manager.register_event('enough_ultraballs') self.event_manager.register_event( 'pokemon_catch_rate', parameters=( 'catch_rate', 'ball_name', 'berry_name', 'berry_count' ) ) self.event_manager.register_event( 'threw_berry', parameters=( 'berry_name', 'ball_name', 'new_catch_rate' ) ) self.event_manager.register_event( 'threw_pokeball', parameters=( 'throw_type', 'spin_label', 'ball_name', 'success_percentage', 'count_left' ) ) self.event_manager.register_event( 'pokemon_capture_failed', parameters=('pokemon',) ) self.event_manager.register_event( 'pokemon_vanished', parameters=( 'pokemon', 'encounter_id', 'latitude', 'longitude', 'pokemon_id' ) ) self.event_manager.register_event('pokemon_not_in_range') self.event_manager.register_event('pokemon_inventory_full') self.event_manager.register_event( 'pokemon_caught', parameters=( 'pokemon', 'ncp', 'cp', 'iv', 'iv_display', 'exp', 'encounter_id', 'latitude', 'longitude', 'pokemon_id' ) ) self.event_manager.register_event( 'pokemon_evolved', parameters=('pokemon', 'iv', 'cp', 'xp', 'candy') ) self.event_manager.register_event('skip_evolve') self.event_manager.register_event('threw_berry_failed', parameters=('status_code',)) self.event_manager.register_event('vip_pokemon') self.event_manager.register_event('gained_candy', parameters=('quantity', 'type')) self.event_manager.register_event('catch_limit') # level up stuff self.event_manager.register_event( 'level_up', parameters=( 'previous_level', 'current_level' ) ) self.event_manager.register_event( 'level_up_reward', parameters=('items',) ) # lucky egg self.event_manager.register_event( 'used_lucky_egg', parameters=('amount_left',) ) self.event_manager.register_event('lucky_egg_error') # softban self.event_manager.register_event('softban') self.event_manager.register_event('softban_fix') self.event_manager.register_event('softban_fix_done') # egg incubating self.event_manager.register_event( 'incubate_try', parameters=( 'incubator_id', 'egg_id' ) ) self.event_manager.register_event( 'incubate', parameters=('distance_in_km',) ) self.event_manager.register_event( 'next_egg_incubates', parameters=('eggs_left', 'eggs_inc', 'eggs') ) self.event_manager.register_event('incubator_already_used') self.event_manager.register_event('egg_already_incubating') self.event_manager.register_event( 'egg_hatched', parameters=( 'pokemon', 'cp', 'iv', 'exp', 'stardust', 'candy' ) ) # discard item self.event_manager.register_event( 'item_discarded', parameters=( 'amount', 'item', 'maximum' ) ) self.event_manager.register_event( 'item_discard_skipped', parameters=('space',) ) self.event_manager.register_event( 'item_discard_fail', parameters=('item',) ) # inventory self.event_manager.register_event('inventory_full') # release self.event_manager.register_event( 'keep_best_release', parameters=( 'amount', 'pokemon', 'criteria' ) ) self.event_manager.register_event( 'future_pokemon_release', parameters=( 'pokemon', 'cp', 'iv', 'below_iv', 'below_cp', 'cp_iv_logic' ) ) self.event_manager.register_event( 'pokemon_release', parameters=('pokemon', 'iv', 'cp', 'candy') ) # polyline walker self.event_manager.register_event( 'polyline_request', parameters=('url',) ) # cluster self.event_manager.register_event( 'found_cluster', parameters=( 'num_points', 'forts', 'radius', 'distance' ) ) self.event_manager.register_event( 'arrived_at_cluster', parameters=( 'num_points', 'forts', 'radius' ) ) # rename self.event_manager.register_event( 'rename_pokemon', parameters=('old_name', 'current_name',) ) self.event_manager.register_event( 'pokemon_nickname_invalid', parameters=('nickname',) ) self.event_manager.register_event( 'unset_pokemon_nickname', parameters=('old_name',) ) # Move To map pokemon self.event_manager.register_event( 'move_to_map_pokemon_fail', parameters=('message',) ) self.event_manager.register_event( 'move_to_map_pokemon_updated_map', parameters=('lat', 'lon') ) self.event_manager.register_event( 'move_to_map_pokemon_teleport_to', parameters=('poke_name', 'poke_dist', 'poke_lat', 'poke_lon', 'disappears_in') ) self.event_manager.register_event( 'move_to_map_pokemon_encounter', parameters=('poke_name', 'poke_dist', 'poke_lat', 'poke_lon', 'disappears_in') ) self.event_manager.register_event( 'move_to_map_pokemon_move_towards', parameters=('poke_name', 'poke_dist', 'poke_lat', 'poke_lon', 'disappears_in') ) self.event_manager.register_event( 'move_to_map_pokemon_teleport_back', parameters=('last_lat', 'last_lon') ) self.event_manager.register_event( 'moving_to_pokemon_throught_fort', parameters=('fort_name', 'distance','poke_name','poke_dist') ) # cached recent_forts self.event_manager.register_event('loaded_cached_forts') self.event_manager.register_event('cached_fort') self.event_manager.register_event( 'no_cached_forts', parameters=('path', ) ) self.event_manager.register_event( 'error_caching_forts', parameters=('path', ) ) # database shit self.event_manager.register_event('catch_log') self.event_manager.register_event('evolve_log') self.event_manager.register_event('login_log') self.event_manager.register_event('transfer_log') self.event_manager.register_event('pokestop_log') self.event_manager.register_event('softban_log') def tick(self): self.health_record.heartbeat() self.cell = self.get_meta_cell() now = time.time() * 1000 for fort in self.cell["forts"]: timeout = fort.get("cooldown_complete_timestamp_ms", 0) if timeout >= now: self.fort_timeouts[fort["id"]] = timeout self.tick_count += 1 # Check if session token has expired self.check_session(self.position) for worker in self.workers: if worker.work() == WorkerResult.RUNNING: return def get_meta_cell(self): location = self.position[0:2] cells = self.find_close_cells(*location) # Combine all cells into a single dict of the items we care about. forts = [] wild_pokemons = [] catchable_pokemons = [] for cell in cells: if "forts" in cell and len(cell["forts"]): forts += cell["forts"] if "wild_pokemons" in cell and len(cell["wild_pokemons"]): wild_pokemons += cell["wild_pokemons"] if "catchable_pokemons" in cell and len(cell["catchable_pokemons"]): catchable_pokemons += cell["catchable_pokemons"] # If there are forts present in the cells sent from the server or we don't yet have any cell data, return all data retrieved if len(forts) > 1 or not self.cell: return { "forts": forts, "wild_pokemons": wild_pokemons, "catchable_pokemons": catchable_pokemons } # If there are no forts present in the data from the server, keep our existing fort data and only update the pokemon cells. else: return { "forts": self.cell["forts"], "wild_pokemons": wild_pokemons, "catchable_pokemons": catchable_pokemons } def update_web_location(self, cells=[], lat=None, lng=None, alt=None): # we can call the function with no arguments and still get the position # and map_cells if lat is None: lat = self.api._position_lat if lng is None: lng = self.api._position_lng if alt is None: alt = self.api._position_alt # dont cache when teleport_to if self.api.teleporting: return if cells == []: location = self.position[0:2] cells = self.find_close_cells(*location) user_data_cells = os.path.join(_base_dir, 'data', 'cells-%s.json' % self.config.username) try: with open(user_data_cells, 'w') as outfile: json.dump(cells, outfile) except IOError as e: self.logger.info('[x] Error while opening location file: %s' % e) user_web_location = os.path.join( _base_dir, 'web', 'location-%s.json' % self.config.username ) # alt is unused atm but makes using *location easier try: with open(user_web_location, 'w') as outfile: json.dump({ 'lat': lat, 'lng': lng, 'alt': alt, 'cells': cells }, outfile) except IOError as e: self.logger.info('[x] Error while opening location file: %s' % e) user_data_lastlocation = os.path.join( _base_dir, 'data', 'last-location-%s.json' % self.config.username ) try: with open(user_data_lastlocation, 'w') as outfile: json.dump({'lat': lat, 'lng': lng, 'alt': alt, 'start_position': self.start_position}, outfile) except IOError as e: self.logger.info('[x] Error while opening location file: %s' % e) def find_close_cells(self, lat, lng): cellid = get_cell_ids(lat, lng) timestamp = [0, ] * len(cellid) response_dict = self.get_map_objects(lat, lng, timestamp, cellid) map_objects = response_dict.get( 'responses', {} ).get('GET_MAP_OBJECTS', {}) status = map_objects.get('status', None) map_cells = [] if status and status == 1: map_cells = map_objects['map_cells'] position = (lat, lng, 0) map_cells.sort( key=lambda x: distance( lat, lng, x['forts'][0]['latitude'], x['forts'][0]['longitude']) if x.get('forts', []) else 1e6 ) return map_cells def _setup_logging(self): # log settings # log format if self.config.debug: log_level = logging.DEBUG logging.getLogger("requests").setLevel(logging.DEBUG) logging.getLogger("websocket").setLevel(logging.DEBUG) logging.getLogger("socketio").setLevel(logging.DEBUG) logging.getLogger("engineio").setLevel(logging.DEBUG) logging.getLogger("socketIO-client").setLevel(logging.DEBUG) logging.getLogger("pgoapi").setLevel(logging.DEBUG) logging.getLogger("rpc_api").setLevel(logging.DEBUG) else: log_level = logging.ERROR logging.getLogger("requests").setLevel(logging.ERROR) logging.getLogger("websocket").setLevel(logging.ERROR) logging.getLogger("socketio").setLevel(logging.ERROR) logging.getLogger("engineio").setLevel(logging.ERROR) logging.getLogger("socketIO-client").setLevel(logging.ERROR) logging.getLogger("pgoapi").setLevel(logging.ERROR) logging.getLogger("rpc_api").setLevel(logging.ERROR) logging.basicConfig( level=log_level, format='%(asctime)s [%(name)10s] [%(levelname)s] %(message)s' ) def check_session(self, position): # Check session expiry if self.api._auth_provider and self.api._auth_provider._ticket_expire: # prevent crash if return not numeric value if not self.is_numeric(self.api._auth_provider._ticket_expire): self.logger.info("Ticket expired value is not numeric", 'yellow') return remaining_time = \ self.api._auth_provider._ticket_expire / 1000 - time.time() if remaining_time < 60: self.event_manager.emit( 'api_error', sender=self, level='info', formatted='Session stale, re-logging in.' ) self.api = ApiWrapper(config=self.config) self.api.set_position(*position) self.login() self.api.activate_signature(self.get_encryption_lib()) @staticmethod def is_numeric(s): try: float(s) return True except ValueError: return False def login(self): self.event_manager.emit( 'login_started', sender=self, level='info', formatted="Login procedure started." ) lat, lng = self.position[0:2] self.api.set_position(lat, lng, self.alt) # or should the alt kept to zero? while not self.api.login( self.config.auth_service, str(self.config.username), str(self.config.password)): self.event_manager.emit( 'login_failed', sender=self, level='info', formatted="Login error, server busy. Waiting 10 seconds to try again." ) time.sleep(10) with self.database as conn: c = conn.cursor() c.execute("SELECT COUNT(name) FROM sqlite_master WHERE type='table' AND name='login'") result = c.fetchone() while True: if result[0] == 1: conn.execute('''INSERT INTO login (timestamp, message) VALUES (?, ?)''', (time.time(), 'LOGIN_SUCCESS')) break else: self.event_manager.emit( 'login_failed', sender=self, level='info', formatted="Login table not founded, skipping log" ) break self.event_manager.emit( 'login_successful', sender=self, level='info', formatted="Login successful." ) def get_encryption_lib(self): if _platform == "Windows" or _platform == "win32": # Check if we are on 32 or 64 bit if sys.maxsize > 2**32: file_name = 'encrypt_64.dll' else: file_name = 'encrypt.dll' else: file_name = 'encrypt.so' if self.config.encrypt_location == '': path = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir)) else: path = self.config.encrypt_location full_path = path + '/'+ file_name if not os.path.isfile(full_path): self.logger.error(file_name + ' is not found! Please place it in the bots root directory or set encrypt_location in config.') self.logger.info('Platform: '+ _platform + ' ' + file_name + ' directory: '+ path) sys.exit(1) else: self.logger.info('Found '+ file_name +'! Platform: ' + _platform + ' ' + file_name + ' directory: ' + path) return full_path def _setup_api(self): # instantiate pgoapi self.api = ApiWrapper(config=self.config) # provide player position on the earth self._set_starting_position() self.login() # chain subrequests (methods) into one RPC call self.api.activate_signature(self.get_encryption_lib()) self.logger.info('') # send empty map_cells and then our position self.update_web_location() def _print_character_info(self): # get player profile call # ---------------------- response_dict = self.api.get_player() # print('Response dictionary: \n\r{}'.format(json.dumps(response_dict, indent=2))) currency_1 = "0" currency_2 = "0" if response_dict: self._player = response_dict['responses']['GET_PLAYER']['player_data'] player = self._player else: self.logger.info( "The API didn't return player info, servers are unstable - " "retrying.", 'red' ) sleep(5) self._print_character_info() # @@@ TODO: Convert this to d/m/Y H:M:S creation_date = datetime.datetime.fromtimestamp( player['creation_timestamp_ms'] / 1e3) creation_date = creation_date.strftime("%Y/%m/%d %H:%M:%S") pokecoins = '0' stardust = '0' items_inventory = inventory.items() if 'amount' in player['currencies'][0]: pokecoins = player['currencies'][0]['amount'] if 'amount' in player['currencies'][1]: stardust = player['currencies'][1]['amount'] self.logger.info('') self.logger.info('--- {username} ---'.format(**player)) self.logger.info( 'Pokemon Bag: {}/{}'.format( inventory.Pokemons.get_space_used(), inventory.get_pokemon_inventory_size() ) ) self.logger.info( 'Items: {}/{}'.format( inventory.Items.get_space_used(), inventory.get_item_inventory_size() ) ) self.logger.info( 'Stardust: {}'.format(stardust) + ' | Pokecoins: {}'.format(pokecoins) ) # Items Output self.logger.info( 'PokeBalls: ' + str(items_inventory.get(1).count) + ' | GreatBalls: ' + str(items_inventory.get(2).count) + ' | UltraBalls: ' + str(items_inventory.get(3).count) + ' | MasterBalls: ' + str(items_inventory.get(4).count)) self.logger.info( 'RazzBerries: ' + str(items_inventory.get(701).count) + ' | BlukBerries: ' + str(items_inventory.get(702).count) + ' | NanabBerries: ' + str(items_inventory.get(703).count)) self.logger.info( 'LuckyEgg: ' + str(items_inventory.get(301).count) + ' | Incubator: ' + str(items_inventory.get(902).count) + ' | TroyDisk: ' + str(items_inventory.get(501).count)) self.logger.info( 'Potion: ' + str(items_inventory.get(101).count) + ' | SuperPotion: ' + str(items_inventory.get(102).count) + ' | HyperPotion: ' + str(items_inventory.get(103).count) + ' | MaxPotion: ' + str(items_inventory.get(104).count)) self.logger.info( 'Incense: ' + str(items_inventory.get(401).count) + ' | IncenseSpicy: ' + str(items_inventory.get(402).count) + ' | IncenseCool: ' + str(items_inventory.get(403).count)) self.logger.info( 'Revive: ' + str(items_inventory.get(201).count) + ' | MaxRevive: ' + str(items_inventory.get(202).count)) self.logger.info('') def _print_list_pokemon(self): # get pokemon list bag = inventory.pokemons().all() id_list =list(set(map(lambda x: x.pokemon_id, bag))) id_list.sort() pokemon_list = [filter(lambda x: x.pokemon_id == y, bag) for y in id_list] show_count = self.config.pokemon_bag_show_count show_candies = self.config.pokemon_bag_show_candies poke_info_displayed = self.config.pokemon_bag_pokemon_info def get_poke_info(info, pokemon): poke_info = { 'cp': 'CP {}'.format(pokemon.cp), 'iv_ads': 'A/D/S {}/{}/{}'.format(pokemon.iv_attack, pokemon.iv_defense, pokemon.iv_stamina), 'iv_pct': 'IV {}'.format(pokemon.iv), 'ivcp': 'IVCP {}'.format(round(pokemon.ivcp,2)), 'ncp': 'NCP {}'.format(round(pokemon.cp_percent,2)), 'level': "Level {}".format(pokemon.level), 'hp': 'HP {}/{}'.format(pokemon.hp, pokemon.hp_max), 'moveset': 'Moves: {}'.format(pokemon.moveset), 'dps': 'DPS {}'.format(round(pokemon.moveset.dps, 2)) } if info not in poke_info: raise ConfigException("info '{}' isn't available for displaying".format(info)) return poke_info[info] self.logger.info('Pokemon:') for pokes in pokemon_list: line_p = '#{} {}'.format(pokes[0].pokemon_id, pokes[0].name) if show_count: line_p += '[{}]'.format(len(pokes)) if show_candies: line_p += '[{} candies]'.format(pokes[0].candy_quantity) line_p += ': ' poke_info = ['({})'.format(', '.join([get_poke_info(x, p) for x in poke_info_displayed])) for p in pokes] self.logger.info(line_p + ' | '.join(poke_info)) self.logger.info('') def use_lucky_egg(self): return self.api.use_item_xp_boost(item_id=301) def _set_starting_position(self): self.event_manager.emit( 'set_start_location', sender=self, level='info', formatted='Setting start location.' ) has_position = False if self.config.test: # TODO: Add unit tests return if self.config.location: location_str = self.config.location location = self.get_pos_by_name(location_str.replace(" ", "")) msg = "Location found: {location} {position}" self.event_manager.emit( 'location_found', sender=self, level='info', formatted=msg, data={ 'location': location_str, 'position': location } ) self.api.set_position(*location) self.event_manager.emit( 'position_update', sender=self, level='info', formatted="Now at {current_position}", data={ 'current_position': self.position, 'last_position': '', 'distance': '', 'distance_unit': '' } ) self.start_position = self.position has_position = True if self.config.location_cache: try: # save location flag used to pull the last known location from # the location.json self.event_manager.emit( 'load_cached_location', sender=self, level='debug', formatted='Loading cached location...' ) with open(os.path.join(_base_dir, 'data', 'last-location-%s.json' % self.config.username)) as f: location_json = json.load(f) location = ( location_json['lat'], location_json['lng'], location_json['alt'], ) # If location has been set in config, only use cache if starting position has not differed if has_position and 'start_position' in location_json: last_start_position = tuple(location_json.get('start_position', [])) # Start position has to have been set on a previous run to do this check if last_start_position and last_start_position != self.start_position: msg = 'Going to a new place, ignoring cached location.' self.event_manager.emit( 'location_cache_ignored', sender=self, level='debug', formatted=msg ) return self.api.set_position(*location) self.event_manager.emit( 'position_update', sender=self, level='debug', formatted='Loaded location {current_position} from cache', data={ 'current_position': location, 'last_position': '', 'distance': '', 'distance_unit': '' } ) has_position = True except Exception: if has_position is False: sys.exit( "No cached Location. Please specify initial location." ) self.event_manager.emit( 'location_cache_error', sender=self, level='debug', formatted='Parsing cached location failed.' ) def get_pos_by_name(self, location_name): # Check if the given location is already a coordinate. if ',' in location_name: possible_coordinates = re.findall( "[-]?\d{1,3}[.]\d{3,7}", location_name ) if len(possible_coordinates) >= 2: # 2 matches, this must be a coordinate. We'll bypass the Google # geocode so we keep the exact location. self.logger.info( '[x] Coordinates found in passed in location, ' 'not geocoding.' ) return float(possible_coordinates[0]), float(possible_coordinates[1]), (float(possible_coordinates[2]) if len(possible_coordinates) == 3 else self.alt) geolocator = GoogleV3(api_key=self.config.gmapkey) loc = geolocator.geocode(location_name, timeout=10) return float(loc.latitude), float(loc.longitude), float(loc.altitude) def heartbeat(self): # Remove forts that we can now spin again. now = time.time() self.fort_timeouts = {id: timeout for id, timeout in self.fort_timeouts.iteritems() if timeout >= now * 1000} if now - self.last_heartbeat >= self.heartbeat_threshold: self.last_heartbeat = now request = self.api.create_request() request.get_player() request.check_awarded_badges() request.call() try: self.web_update_queue.put_nowait(True) # do this outside of thread every tick except Queue.Full: pass def update_web_location_worker(self): while True: self.web_update_queue.get() self.update_web_location() def display_player_info(self): inventory_items = self.api.get_inventory() inventory_items = inventory_items['responses']['GET_INVENTORY']['inventory_delta']['inventory_items'] player_stats = next((x["inventory_item_data"]["player_stats"] for x in inventory_items if x.get("inventory_item_data", {}).get("player_stats", {})), None) if player_stats: nextlvlxp = (int(player_stats.get('next_level_xp', 0)) - int(player_stats.get('experience', 0))) if 'level' in player_stats and 'experience' in player_stats: self.logger.info( 'Level: {level}'.format( **player_stats) + ' (Next Level: {} XP)'.format( nextlvlxp) + ' (Total: {experience} XP)' ''.format(**player_stats)) if 'pokemons_captured' in player_stats and 'poke_stop_visits' in player_stats: self.logger.info( 'Pokemon Captured: ' '{pokemons_captured}'.format( **player_stats) + ' | Pokestops Visited: ' '{poke_stop_visits}'.format( **player_stats)) def get_forts(self, order_by_distance=False): forts = [fort for fort in self.cell['forts'] if 'latitude' in fort and 'type' in fort] if order_by_distance: forts.sort(key=lambda x: distance( self.position[0], self.position[1], x['latitude'], x['longitude'] )) return forts def get_map_objects(self, lat, lng, timestamp, cellid): if time.time() - self.last_time_map_object < self.config.map_object_cache_time: return self.last_map_object self.last_map_object = self.api.get_map_objects( latitude=f2i(lat), longitude=f2i(lng), since_timestamp_ms=timestamp, cell_id=cellid ) self.last_time_map_object = time.time() return self.last_map_object def _load_recent_forts(self): if not self.config.forts_cache_recent_forts: return cached_forts_path = os.path.join(_base_dir, 'data', 'recent-forts-%s.json' % self.config.username) try: # load the cached recent forts with open(cached_forts_path) as f: cached_recent_forts = json.load(f) num_cached_recent_forts = len(cached_recent_forts) num_recent_forts = len(self.recent_forts) # Handles changes in max_circle_size if not num_recent_forts: self.recent_forts = [] elif num_recent_forts > num_cached_recent_forts: self.recent_forts[-num_cached_recent_forts:] = cached_recent_forts elif num_recent_forts < num_cached_recent_forts: self.recent_forts = cached_recent_forts[-num_recent_forts:] else: self.recent_forts = cached_recent_forts self.event_manager.emit( 'loaded_cached_forts', sender=self, level='debug', formatted='Loaded cached forts...' ) except IOError: self.event_manager.emit( 'no_cached_forts', sender=self, level='debug', formatted='Starting new cached forts for {path}', data={'path': cached_forts_path} )
class PokemonGoBot(object): @property def position(self): return self.api._position_lat, self.api._position_lng, 0 @position.setter def position(self, position_tuple): self.api._position_lat, self.api._position_lng, self.api._position_alt = position_tuple @property def player_data(self): """ Returns the player data as received from the API. :return: The player data. :rtype: dict """ return self._player def __init__(self, config): self.config = config self.fort_timeouts = dict() self.pokemon_list = json.load( open(os.path.join('data', 'pokemon.json'))) self.item_list = json.load(open(os.path.join('data', 'items.json'))) self.metrics = Metrics(self) self.latest_inventory = None self.cell = None self.recent_forts = [None] * config.forts_max_circle_size self.tick_count = 0 self.softban = False self.start_position = None self.last_map_object = None self.last_time_map_object = 0 self.logger = logging.getLogger(type(self).__name__) # Make our own copy of the workers for this instance self.workers = [] # Theading setup for file writing self.web_update_queue = Queue.Queue(maxsize=1) self.web_update_thread = threading.Thread( target=self.update_web_location_worker) self.web_update_thread.start() def start(self): self._setup_event_system() self._setup_logging() self._setup_api() random.seed() def _setup_event_system(self): handlers = [LoggingHandler()] if self.config.websocket_server_url: if self.config.websocket_start_embedded_server: self.sio_runner = SocketIoRunner( self.config.websocket_server_url) self.sio_runner.start_listening_async() websocket_handler = SocketIoHandler( self, self.config.websocket_server_url) handlers.append(websocket_handler) if self.config.websocket_remote_control: remote_control = WebsocketRemoteControl(self).start() self.event_manager = EventManager(*handlers) self._register_events() if self.config.show_events: self.event_manager.event_report() sys.exit(1) # Registering event: # self.event_manager.register_event("location", parameters=['lat', 'lng']) # # Emitting event should be enough to add logging and send websocket # message: : # self.event_manager.emit('location', 'level'='info', data={'lat': 1, 'lng':1}), def _register_events(self): self.event_manager.register_event('location_found', parameters=('position', 'location')) self.event_manager.register_event('api_error') self.event_manager.register_event('config_error') self.event_manager.register_event('login_started') self.event_manager.register_event('login_failed') self.event_manager.register_event('login_successful') self.event_manager.register_event('set_start_location') self.event_manager.register_event('load_cached_location') self.event_manager.register_event('location_cache_ignored') self.event_manager.register_event( 'position_update', parameters=( 'current_position', 'last_position', 'distance', # optional 'distance_unit' # optional )) self.event_manager.register_event('location_cache_error') self.event_manager.register_event('bot_start') self.event_manager.register_event('bot_exit') # sleep stuff self.event_manager.register_event('next_sleep', parameters=('time', )) self.event_manager.register_event('bot_sleep', parameters=('time_in_seconds', )) # fort stuff self.event_manager.register_event('spun_fort', parameters=('fort_id', 'latitude', 'longitude')) self.event_manager.register_event('lured_pokemon_found', parameters=('fort_id', 'fort_name', 'encounter_id', 'latitude', 'longitude')) self.event_manager.register_event('moving_to_fort', parameters=('fort_name', 'distance')) self.event_manager.register_event('moving_to_lured_fort', parameters=('fort_name', 'distance', 'lure_distance')) self.event_manager.register_event('spun_pokestop', parameters=('pokestop', 'exp', 'items')) self.event_manager.register_event('pokestop_empty', parameters=('pokestop', )) self.event_manager.register_event('pokestop_out_of_range', parameters=('pokestop', )) self.event_manager.register_event('pokestop_on_cooldown', parameters=('pokestop', 'minutes_left')) self.event_manager.register_event('unknown_spin_result', parameters=('status_code', )) self.event_manager.register_event('pokestop_searching_too_often') self.event_manager.register_event('arrived_at_fort') # pokemon stuff self.event_manager.register_event( 'catchable_pokemon', parameters=('pokemon_id', 'spawn_point_id', 'encounter_id', 'latitude', 'longitude', 'expiration_timestamp_ms')) self.event_manager.register_event('pokemon_appeared', parameters=( 'pokemon', 'cp', 'iv', 'iv_display', )) self.event_manager.register_event('pokemon_catch_rate', parameters=('catch_rate', 'berry_name', 'berry_count')) self.event_manager.register_event('threw_berry', parameters=('berry_name', 'new_catch_rate')) self.event_manager.register_event('threw_pokeball', parameters=('pokeball', 'success_percentage', 'count_left')) self.event_manager.register_event('pokemon_escaped', parameters=('pokemon', )) self.event_manager.register_event('pokemon_vanished', parameters=('pokemon', )) self.event_manager.register_event('pokemon_caught', parameters=('pokemon', 'cp', 'iv', 'iv_display', 'exp')) self.event_manager.register_event('pokemon_evolved', parameters=('pokemon', 'iv', 'cp')) self.event_manager.register_event('pokemon_evolve_fail', parameters=('pokemon', )) self.event_manager.register_event('skip_evolve') self.event_manager.register_event('threw_berry_failed', parameters=('status_code', )) self.event_manager.register_event('vip_pokemon') # level up stuff self.event_manager.register_event('level_up', parameters=('previous_level', 'current_level')) self.event_manager.register_event('level_up_reward', parameters=('items', )) # lucky egg self.event_manager.register_event('used_lucky_egg', parameters=('amount_left', )) self.event_manager.register_event('lucky_egg_error') # softban self.event_manager.register_event('softban') self.event_manager.register_event('softban_fix') self.event_manager.register_event('softban_fix_done') # egg incubating self.event_manager.register_event('incubate_try', parameters=('incubator_id', 'egg_id')) self.event_manager.register_event('incubate', parameters=('distance_in_km', )) self.event_manager.register_event('next_egg_incubates', parameters=('distance_in_km', )) self.event_manager.register_event('incubator_already_used') self.event_manager.register_event('egg_already_incubating') self.event_manager.register_event('egg_hatched', parameters=('pokemon', 'cp', 'iv', 'exp', 'stardust', 'candy')) # discard item self.event_manager.register_event('item_discarded', parameters=('amount', 'item', 'maximum')) self.event_manager.register_event('item_discard_fail', parameters=('item', )) # inventory self.event_manager.register_event('inventory_full') # release self.event_manager.register_event('keep_best_release', parameters=('amount', 'pokemon', 'criteria')) self.event_manager.register_event('future_pokemon_release', parameters=('pokemon', 'cp', 'iv', 'below_iv', 'below_cp', 'cp_iv_logic')) self.event_manager.register_event('pokemon_release', parameters=('pokemon', 'cp', 'iv')) # polyline walker self.event_manager.register_event('polyline_request', parameters=('url', )) # cluster self.event_manager.register_event('found_cluster', parameters=('num_points', 'forts', 'radius', 'distance')) self.event_manager.register_event('arrived_at_cluster', parameters=('forts', 'radius')) # rename self.event_manager.register_event('rename_pokemon', parameters=('old_name', 'current_name')) self.event_manager.register_event('pokemon_nickname_invalid', parameters=('nickname', )) self.event_manager.register_event('unset_pokemon_nickname') # Move To map pokemon self.event_manager.register_event('move_to_map_pokemon_fail', parameters=('message', )) self.event_manager.register_event('move_to_map_pokemon_updated_map', parameters=('lat', 'lon')) self.event_manager.register_event('move_to_map_pokemon_teleport_to', parameters=('poke_name', 'poke_dist', 'poke_lat', 'poke_lon', 'disappears_in')) self.event_manager.register_event('move_to_map_pokemon_encounter', parameters=('poke_name', 'poke_dist', 'poke_lat', 'poke_lon', 'disappears_in')) self.event_manager.register_event('move_to_map_pokemon_move_towards', parameters=('poke_name', 'poke_dist', 'poke_lat', 'poke_lon', 'disappears_in')) self.event_manager.register_event('move_to_map_pokemon_teleport_back', parameters=('last_lat', 'last_lon')) def tick(self): self.health_record.heartbeat() self.cell = self.get_meta_cell() self.tick_count += 1 # Check if session token has expired self.check_session(self.position[0:2]) for worker in self.workers: if worker.work() == WorkerResult.RUNNING: return def get_meta_cell(self): location = self.position[0:2] cells = self.find_close_cells(*location) # Combine all cells into a single dict of the items we care about. forts = [] wild_pokemons = [] catchable_pokemons = [] for cell in cells: if "forts" in cell and len(cell["forts"]): forts += cell["forts"] if "wild_pokemons" in cell and len(cell["wild_pokemons"]): wild_pokemons += cell["wild_pokemons"] if "catchable_pokemons" in cell and len( cell["catchable_pokemons"]): catchable_pokemons += cell["catchable_pokemons"] # If there are forts present in the cells sent from the server or we don't yet have any cell data, return all data retrieved if len(forts) > 1 or not self.cell: return { "forts": forts, "wild_pokemons": wild_pokemons, "catchable_pokemons": catchable_pokemons } # If there are no forts present in the data from the server, keep our existing fort data and only update the pokemon cells. else: return { "forts": self.cell["forts"], "wild_pokemons": wild_pokemons, "catchable_pokemons": catchable_pokemons } def update_web_location(self, cells=[], lat=None, lng=None, alt=None): # we can call the function with no arguments and still get the position # and map_cells if lat is None: lat = self.api._position_lat if lng is None: lng = self.api._position_lng if alt is None: alt = 0 if cells == []: location = self.position[0:2] cells = self.find_close_cells(*location) # insert detail info about gym to fort for cell in cells: if 'forts' in cell: for fort in cell['forts']: if fort.get('type') != 1: response_gym_details = self.api.get_gym_details( gym_id=fort.get('id'), player_latitude=lng, player_longitude=lat, gym_latitude=fort.get('latitude'), gym_longitude=fort.get('longitude')) fort['gym_details'] = response_gym_details.get( 'responses', {}).get('GET_GYM_DETAILS', None) user_data_cells = "data/cells-%s.json" % self.config.username with open(user_data_cells, 'w') as outfile: json.dump(cells, outfile) user_web_location = os.path.join( 'web', 'location-%s.json' % self.config.username) # alt is unused atm but makes using *location easier try: with open(user_web_location, 'w') as outfile: json.dump({ 'lat': lat, 'lng': lng, 'alt': alt, 'cells': cells }, outfile) except IOError as e: self.logger.info('[x] Error while opening location file: %s' % e) user_data_lastlocation = os.path.join( 'data', 'last-location-%s.json' % self.config.username) try: with open(user_data_lastlocation, 'w') as outfile: json.dump( { 'lat': lat, 'lng': lng, 'start_position': self.start_position }, outfile) except IOError as e: self.logger.info('[x] Error while opening location file: %s' % e) def find_close_cells(self, lat, lng): cellid = get_cell_ids(lat, lng) timestamp = [ 0, ] * len(cellid) response_dict = self.get_map_objects(lat, lng, timestamp, cellid) map_objects = response_dict.get('responses', {}).get('GET_MAP_OBJECTS', {}) status = map_objects.get('status', None) map_cells = [] if status and status == 1: map_cells = map_objects['map_cells'] position = (lat, lng, 0) map_cells.sort( key=lambda x: distance(lat, lng, x['forts'][0]['latitude'], x[ 'forts'][0]['longitude']) if x.get('forts', []) else 1e6) return map_cells def _setup_logging(self): # log settings # log format if self.config.debug: log_level = logging.DEBUG logging.getLogger("requests").setLevel(logging.DEBUG) logging.getLogger("websocket").setLevel(logging.DEBUG) logging.getLogger("socketio").setLevel(logging.DEBUG) logging.getLogger("engineio").setLevel(logging.DEBUG) logging.getLogger("socketIO-client").setLevel(logging.DEBUG) logging.getLogger("pgoapi").setLevel(logging.DEBUG) logging.getLogger("rpc_api").setLevel(logging.DEBUG) else: log_level = logging.ERROR logging.getLogger("requests").setLevel(logging.ERROR) logging.getLogger("websocket").setLevel(logging.ERROR) logging.getLogger("socketio").setLevel(logging.ERROR) logging.getLogger("engineio").setLevel(logging.ERROR) logging.getLogger("socketIO-client").setLevel(logging.ERROR) logging.getLogger("pgoapi").setLevel(logging.ERROR) logging.getLogger("rpc_api").setLevel(logging.ERROR) logging.basicConfig( level=log_level, format='%(asctime)s [%(name)10s] [%(levelname)s] %(message)s') def check_session(self, position): # Check session expiry if self.api._auth_provider and self.api._auth_provider._ticket_expire: # prevent crash if return not numeric value if not self.is_numeric(self.api._auth_provider._ticket_expire): self.logger.info("Ticket expired value is not numeric", 'yellow') return remaining_time = \ self.api._auth_provider._ticket_expire / 1000 - time.time() if remaining_time < 60: self.event_manager.emit( 'api_error', sender=self, level='info', formatted='Session stale, re-logging in.') position = self.position self.api = ApiWrapper() self.position = position self.login() self.api.activate_signature(self.get_encryption_lib()) @staticmethod def is_numeric(s): try: float(s) return True except ValueError: return False def login(self): self.event_manager.emit('login_started', sender=self, level='info', formatted="Login procedure started.") lat, lng = self.position[0:2] self.api.set_position(lat, lng, 0) while not self.api.login(self.config.auth_service, str(self.config.username), str(self.config.password)): self.event_manager.emit( 'login_failed', sender=self, level='info', formatted= "Login error, server busy. Waiting 10 seconds to try again.") time.sleep(10) self.event_manager.emit('login_successful', sender=self, level='info', formatted="Login successful.") def get_encryption_lib(self): if _platform == "linux" or _platform == "linux2" or _platform == "darwin": file_name = 'encrypt.so' elif _platform == "Windows" or _platform == "win32": # Check if we are on 32 or 64 bit if sys.maxsize > 2**32: file_name = 'encrypt_64.dll' else: file_name = 'encrypt.dll' if self.config.encrypt_location == '': path = os.path.abspath( os.path.join(os.path.dirname(__file__), os.pardir)) else: path = self.config.encrypt_location full_path = path + '/' + file_name if not os.path.isfile(full_path): self.logger.error( file_name + ' is not found! Please place it in the bots root directory or set libencrypt_location in config.' ) self.logger.info('Platform: ' + _platform + ' Encrypt.so directory: ' + path) sys.exit(1) else: self.logger.info('Found ' + file_name + '! Platform: ' + _platform + ' Encrypt.so directory: ' + path) return full_path def _setup_api(self): # instantiate pgoapi self.api = ApiWrapper() # provide player position on the earth self._set_starting_position() self.login() # chain subrequests (methods) into one RPC call self._print_character_info() self.api.activate_signature(self.get_encryption_lib()) self.logger.info('') self.update_inventory() # send empty map_cells and then our position self.update_web_location() def _print_character_info(self): # get player profile call # ---------------------- response_dict = self.api.get_player() # print('Response dictionary: \n\r{}'.format(json.dumps(response_dict, indent=2))) currency_1 = "0" currency_2 = "0" if response_dict: self._player = response_dict['responses']['GET_PLAYER'][ 'player_data'] player = self._player else: self.logger.info( "The API didn't return player info, servers are unstable - " "retrying.", 'red') sleep(5) self._print_character_info() # @@@ TODO: Convert this to d/m/Y H:M:S creation_date = datetime.datetime.fromtimestamp( player['creation_timestamp_ms'] / 1e3) creation_date = creation_date.strftime("%Y/%m/%d %H:%M:%S") pokecoins = '0' stardust = '0' items_stock = self.current_inventory() if 'amount' in player['currencies'][0]: pokecoins = player['currencies'][0]['amount'] if 'amount' in player['currencies'][1]: stardust = player['currencies'][1]['amount'] self.logger.info('') self.logger.info('--- {username} ---'.format(**player)) self.get_player_info() self.logger.info('Pokemon Bag: {}/{}'.format( self.get_inventory_count('pokemon'), player['max_pokemon_storage'])) self.logger.info('Items: {}/{}'.format( self.get_inventory_count('item'), player['max_item_storage'])) self.logger.info('Stardust: {}'.format(stardust) + ' | Pokecoins: {}'.format(pokecoins)) # Items Output self.logger.info('PokeBalls: ' + str(items_stock[1]) + ' | GreatBalls: ' + str(items_stock[2]) + ' | UltraBalls: ' + str(items_stock[3])) self.logger.info('RazzBerries: ' + str(items_stock[701]) + ' | BlukBerries: ' + str(items_stock[702]) + ' | NanabBerries: ' + str(items_stock[703])) self.logger.info('LuckyEgg: ' + str(items_stock[301]) + ' | Incubator: ' + str(items_stock[902]) + ' | TroyDisk: ' + str(items_stock[501])) self.logger.info('Potion: ' + str(items_stock[101]) + ' | SuperPotion: ' + str(items_stock[102]) + ' | HyperPotion: ' + str(items_stock[103]) + ' | MaxPotion: ' + str(items_stock[104])) self.logger.info('Incense: ' + str(items_stock[401]) + ' | IncenseSpicy: ' + str(items_stock[402]) + ' | IncenseCool: ' + str(items_stock[403])) self.logger.info('Revive: ' + str(items_stock[201]) + ' | MaxRevive: ' + str(items_stock[202])) self.logger.info('') def use_lucky_egg(self): return self.api.use_item_xp_boost(item_id=301) def get_inventory(self): if self.latest_inventory is None: self.latest_inventory = self.api.get_inventory() return self.latest_inventory def update_inventory(self): response = self.get_inventory() self.inventory = list() inventory_items = response.get('responses', {}).get( 'GET_INVENTORY', {}).get('inventory_delta', {}).get('inventory_items', {}) if inventory_items: for item in inventory_items: item_info = item.get('inventory_item_data', {}).get('item', {}) if {"item_id", "count"}.issubset(set(item_info.keys())): self.inventory.append(item['inventory_item_data']['item']) def current_inventory(self): inventory_req = self.get_inventory() inventory_dict = inventory_req['responses']['GET_INVENTORY'][ 'inventory_delta']['inventory_items'] user_web_inventory = 'web/inventory-%s.json' % self.config.username with open(user_web_inventory, 'w') as outfile: json.dump(inventory_dict, outfile) # get player items stock # ---------------------- items_stock = {x.value: 0 for x in list(Item)} for item in inventory_dict: item_dict = item.get('inventory_item_data', {}).get('item', {}) item_count = item_dict.get('count') item_id = item_dict.get('item_id') if item_count and item_id: if item_id in items_stock: items_stock[item_id] = item_count return items_stock def item_inventory_count(self, id): inventory_req = self.get_inventory() inventory_dict = inventory_req['responses']['GET_INVENTORY'][ 'inventory_delta']['inventory_items'] if id == 'all': return self._all_items_inventory_count(inventory_dict) else: return self._item_inventory_count_per_id(id, inventory_dict) def _item_inventory_count_per_id(self, id, inventory_dict): item_count = 0 for item in inventory_dict: item_dict = item.get('inventory_item_data', {}).get('item', {}) item_id = item_dict.get('item_id', False) item_count = item_dict.get('count', False) if item_id == int(id) and item_count: return item_count return 0 def _all_items_inventory_count(self, inventory_dict): item_count_dict = {} for item in inventory_dict: item_dict = item.get('inventory_item_data', {}).get('item', {}) item_id = item_dict.get('item_id', False) item_count = item_dict.get('count', False) if item_id and item_count: item_count_dict[item_id] = item_count return item_count_dict def _set_starting_position(self): self.event_manager.emit('set_start_location', sender=self, level='info', formatted='Setting start location.') has_position = False if self.config.test: # TODO: Add unit tests return if self.config.location: location_str = self.config.location location = self.get_pos_by_name(location_str.replace(" ", "")) msg = "Location found: {location} {position}" self.event_manager.emit('location_found', sender=self, level='info', formatted=msg, data={ 'location': location_str, 'position': location }) self.api.set_position(*location) self.event_manager.emit('position_update', sender=self, level='info', formatted="Now at {current_position}", data={ 'current_position': self.position, 'last_position': '', 'distance': '', 'distance_unit': '' }) self.start_position = self.position has_position = True if self.config.location_cache: try: # save location flag used to pull the last known location from # the location.json self.event_manager.emit('load_cached_location', sender=self, level='debug', formatted='Loading cached location...') with open('data/last-location-%s.json' % self.config.username) as f: location_json = json.load(f) location = (location_json['lat'], location_json['lng'], 0.0) # If location has been set in config, only use cache if starting position has not differed if has_position and 'start_position' in location_json: last_start_position = tuple( location_json.get('start_position', [])) # Start position has to have been set on a previous run to do this check if last_start_position and last_start_position != self.start_position: msg = 'Going to a new place, ignoring cached location.' self.event_manager.emit('location_cache_ignored', sender=self, level='debug', formatted=msg) return self.api.set_position(*location) self.event_manager.emit( 'position_update', sender=self, level='debug', formatted='Loaded location {current_position} from cache', data={ 'current_position': location, 'last_position': '', 'distance': '', 'distance_unit': '' }) has_position = True except Exception: if has_position is False: sys.exit( "No cached Location. Please specify initial location.") self.event_manager.emit( 'location_cache_error', sender=self, level='debug', formatted='Parsing cached location failed.') def get_pos_by_name(self, location_name): # Check if the given location is already a coordinate. if ',' in location_name: possible_coordinates = re.findall("[-]?\d{1,3}[.]\d{3,7}", location_name) if len(possible_coordinates) == 2: # 2 matches, this must be a coordinate. We'll bypass the Google # geocode so we keep the exact location. self.logger.info( '[x] Coordinates found in passed in location, ' 'not geocoding.') return float(possible_coordinates[0]), float( possible_coordinates[1]), float("0.0") geolocator = GoogleV3(api_key=self.config.gmapkey) loc = geolocator.geocode(location_name, timeout=10) return float(loc.latitude), float(loc.longitude), float(loc.altitude) def heartbeat(self): # Remove forts that we can now spin again. self.fort_timeouts = { id: timeout for id, timeout in self.fort_timeouts.iteritems() if timeout >= time.time() * 1000 } request = self.api.create_request() request.get_player() request.check_awarded_badges() request.call() try: self.web_update_queue.put_nowait( True) # do this outside of thread every tick except Queue.Full: pass def update_web_location_worker(self): while True: self.web_update_queue.get() self.update_web_location() def get_inventory_count(self, what): response_dict = self.get_inventory() inventory_items = response_dict.get('responses', {}).get( 'GET_INVENTORY', {}).get('inventory_delta', {}).get('inventory_items', {}) if inventory_items: pokecount = 0 itemcount = 1 for item in inventory_items: if 'inventory_item_data' in item: if 'pokemon_data' in item['inventory_item_data']: pokecount += 1 itemcount += item['inventory_item_data'].get( 'item', {}).get('count', 0) if 'pokemon' in what: return pokecount if 'item' in what: return itemcount return '0' def get_player_info(self): response_dict = self.get_inventory() inventory_items = response_dict.get('responses', {}).get( 'GET_INVENTORY', {}).get('inventory_delta', {}).get('inventory_items', {}) if inventory_items: pokecount = 0 itemcount = 1 for item in inventory_items: # print('item {}'.format(item)) playerdata = item.get('inventory_item_data', {}).get('player_stats') if playerdata: nextlvlxp = (int(playerdata.get('next_level_xp', 0)) - int(playerdata.get('experience', 0))) if 'level' in playerdata and 'experience' in playerdata: self.logger.info( 'Level: {level}'.format(**playerdata) + ' (Next Level: {} XP)'.format(nextlvlxp) + ' (Total: {experience} XP)' ''.format(**playerdata)) if 'pokemons_captured' in playerdata and 'poke_stop_visits' in playerdata: self.logger.info( 'Pokemon Captured: ' '{pokemons_captured}'.format(**playerdata) + ' | Pokestops Visited: ' '{poke_stop_visits}'.format(**playerdata)) def has_space_for_loot(self): number_of_things_gained_by_stop = 5 enough_space = ( self.get_inventory_count('item') < self._player['max_item_storage'] - number_of_things_gained_by_stop) return enough_space def get_forts(self, order_by_distance=False): forts = [ fort for fort in self.cell['forts'] if 'latitude' in fort and 'type' in fort ] if order_by_distance: forts.sort(key=lambda x: distance(self.position[0], self.position[ 1], x['latitude'], x['longitude'])) return forts def get_map_objects(self, lat, lng, timestamp, cellid): if time.time( ) - self.last_time_map_object < self.config.map_object_cache_time: return self.last_map_object self.last_map_object = self.api.get_map_objects( latitude=f2i(lat), longitude=f2i(lng), since_timestamp_ms=timestamp, cell_id=cellid) self.last_time_map_object = time.time() return self.last_map_object