From f50c3a66d5a1d42109956fde537cd31b90e057b6 Mon Sep 17 00:00:00 2001 From: Astral Date: Fri, 29 Jul 2016 15:42:10 -0700 Subject: [PATCH 1/4] Updated item_filter in config.json.example to use item names instead of item id's --- configs/config.json.example | 12 ++++----- .../cell_workers/recycle_items_worker.py | 25 ++++++------------- 2 files changed, 13 insertions(+), 24 deletions(-) diff --git a/configs/config.json.example b/configs/config.json.example index 2e7cd45433..1ee5083b9e 100644 --- a/configs/config.json.example +++ b/configs/config.json.example @@ -22,12 +22,12 @@ "distance_unit": "km", "reconnecting_timeout": 15, "item_filter": { - "1": { "keep" : 100 }, - "101": { "keep" : 10 }, - "102": { "keep" : 30 }, - "103": { "keep" : 30 }, - "201": { "keep" : 30 }, - "701": { "keep" : 100 } + "Pokeball": { "keep" : 100 }, + "Potion": { "keep" : 10 }, + "Super Potion": { "keep" : 20 }, + "Hyper Potion": { "keep" : 30 }, + "Revive": { "keep" : 30 }, + "Razz Berry": { "keep" : 100 } }, "evolve_all": "NONE", "evolve_speed": 20, diff --git a/pokemongo_bot/cell_workers/recycle_items_worker.py b/pokemongo_bot/cell_workers/recycle_items_worker.py index 63116b0e6b..953a2c8c05 100644 --- a/pokemongo_bot/cell_workers/recycle_items_worker.py +++ b/pokemongo_bot/cell_workers/recycle_items_worker.py @@ -15,33 +15,22 @@ def work(self): for item_id, bag_count in item_count_dict.iteritems(): item_name = self.item_list[str(item_id)] - id_filter = self.config.item_filter.get(str(item_id), 0) + id_filter = self.config.item_filter.get(item_name, 0) if id_filter is not 0: id_filter_keep = id_filter.get('keep', 20) bag_count = self.bot.item_inventory_count(item_id) - if str(item_id) in self.config.item_filter and bag_count > id_filter_keep: + if item_name in self.config.item_filter and bag_count > id_filter_keep: items_recycle_count = bag_count - id_filter_keep - - response_dict_recycle = self.send_recycle_item_request( - item_id=item_id, - count=items_recycle_count - ) - - result = response_dict_recycle.get('responses', {}) \ - .get('RECYCLE_INVENTORY_ITEM', {}) \ - .get('result', 0) + response_dict_recycle = self.send_recycle_item_request(item_id=item_id, count=items_recycle_count) + result = response_dict_recycle.get('responses', {}).get('RECYCLE_INVENTORY_ITEM', {}).get('result', 0) if result == 1: # Request success - message_template = "-- Recycled {}x {} (keeps only {} maximum) " - message = message_template.format( - str(items_recycle_count), - item_name, - str(id_filter_keep) - ) + message_template = "-- Discarded {}x {} (keeps only {} maximum) " + message = message_template.format(str(items_recycle_count), item_name, str(id_filter_keep)) logger.log(message, 'green') else: - logger.log("-- Failed to recycle " + item_name + "has failed!", 'red') + logger.log("-- Failed to discard " + item_name, 'red') def send_recycle_item_request(self, item_id, count): self.api.recycle_inventory_item(item_id=item_id, count=count) From db580a4198c24537a60b58c6c21ca50f85941656 Mon Sep 17 00:00:00 2001 From: Astral Date: Sat, 30 Jul 2016 04:05:37 -0700 Subject: [PATCH 2/4] Config.json item_list verification --- pokecli.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pokecli.py b/pokecli.py index af31bd79aa..362f02659d 100755 --- a/pokecli.py +++ b/pokecli.py @@ -394,6 +394,12 @@ def init_config(): parser.error("--catch_randomize_spin_factor is out of range! (should be 0 <= catch_randomize_spin_factor <= 1)") return None + # item list config verification + item_list = json.load(open(os.path.join('data', 'items.json'))) + for config_item_name, bag_count in config.item_filter.iteritems(): + if config_item_name not in item_list.viewvalues(): + raise Exception('Config.json error - item "' + config_item_name + '" does not exist (spelling mistake?)') + # create web dir if not exists try: os.makedirs(web_dir) From cc80470a9c0dd84496d2c5beb723ef2ad826a6f0 Mon Sep 17 00:00:00 2001 From: Astral Date: Sat, 30 Jul 2016 13:47:06 -0700 Subject: [PATCH 3/4] Merge remote-tracking branch 'upstream/dev' into dev Conflicts: pokemongo_bot/cell_workers/recycle_items.py --- .dockerignore | 3 +- .gitignore | 6 +- .travis.yml | 4 +- Dockerfile | 7 + README.md | 2 +- configs/config.json.example | 26 +- configs/config.json.path.example | 66 +++++ configs/config.json.pokemon.example | 26 +- configs/path.example.json | 6 + docker-compose.yml | 20 ++ pokecli.py | 93 +++++-- pokemongo_bot/__init__.py | 253 ++++++++++++------ pokemongo_bot/cell_workers/__init__.py | 18 +- ...kemon_worker.py => catch_lured_pokemon.py} | 17 +- .../cell_workers/catch_visible_pokemon.py | 43 +++ .../catch_visible_pokemon_worker.py | 47 ---- .../{evolve_all_worker.py => evolve_all.py} | 24 +- ...{soft_ban_worker.py => handle_soft_ban.py} | 14 +- ...cubate_eggs_worker.py => incubate_eggs.py} | 55 ++-- ...move_to_fort_worker.py => move_to_fort.py} | 33 ++- .../cell_workers/pokemon_catch_worker.py | 203 +++++++++----- ...cycle_items_worker.py => recycle_items.py} | 36 ++- .../{seen_fort_worker.py => spin_fort.py} | 30 +-- ...transfer_worker.py => transfer_pokemon.py} | 30 ++- pokemongo_bot/cell_workers/utils.py | 20 ++ pokemongo_bot/logger.py | 14 +- pokemongo_bot/navigators/__init__.py | 2 + pokemongo_bot/navigators/path_navigator.py | 96 +++++++ .../{ => navigators}/spiral_navigator.py | 25 +- .../test/tree_config_builder_test.py | 64 +++++ pokemongo_bot/tree_config_builder.py | 35 +++ requirements.txt | 1 + 32 files changed, 968 insertions(+), 351 deletions(-) create mode 100644 configs/config.json.path.example create mode 100644 configs/path.example.json create mode 100644 docker-compose.yml rename pokemongo_bot/cell_workers/{catch_lured_pokemon_worker.py => catch_lured_pokemon.py} (71%) create mode 100644 pokemongo_bot/cell_workers/catch_visible_pokemon.py delete mode 100644 pokemongo_bot/cell_workers/catch_visible_pokemon_worker.py rename pokemongo_bot/cell_workers/{evolve_all_worker.py => evolve_all.py} (94%) rename pokemongo_bot/cell_workers/{soft_ban_worker.py => handle_soft_ban.py} (85%) rename pokemongo_bot/cell_workers/{incubate_eggs_worker.py => incubate_eggs.py} (73%) rename pokemongo_bot/cell_workers/{move_to_fort_worker.py => move_to_fort.py} (62%) rename pokemongo_bot/cell_workers/{recycle_items_worker.py => recycle_items.py} (55%) rename pokemongo_bot/cell_workers/{seen_fort_worker.py => spin_fort.py} (85%) rename pokemongo_bot/cell_workers/{pokemon_transfer_worker.py => transfer_pokemon.py} (90%) create mode 100644 pokemongo_bot/navigators/__init__.py create mode 100644 pokemongo_bot/navigators/path_navigator.py rename pokemongo_bot/{ => navigators}/spiral_navigator.py (79%) create mode 100644 pokemongo_bot/test/tree_config_builder_test.py create mode 100644 pokemongo_bot/tree_config_builder.py diff --git a/.dockerignore b/.dockerignore index 67557304fc..b08db7b967 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,3 +1,4 @@ .idea .git* -configs/config.json \ No newline at end of file +**/*config.json +**/*userdata.js \ No newline at end of file diff --git a/.gitignore b/.gitignore index 99692df170..58f90e86e3 100644 --- a/.gitignore +++ b/.gitignore @@ -102,16 +102,18 @@ web/ data/last-location*.json data/cells-*.json -#Multiple config +# Multiple config configs/* !configs/config.json.example !configs/release_config.json.example !configs/config.json.pokemons.example !configs/config.json.pokemon.example +!configs/config.json.path.example +!configs/path.example.json # Virtualenv folders bin/ include/ -#Pip check file +# Pip check file pip-selfcheck.json diff --git a/.travis.yml b/.travis.yml index bffb4ce251..34e5a3e0e6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,4 +10,6 @@ addons: install: - pip install -r requirements.txt - pip install pylint -script: "python pylint-recursive.py" +script: + - python pylint-recursive.py + - python -m unittest discover -s pokemongo_bot -p "*_test.py" diff --git a/Dockerfile b/Dockerfile index b4ab99956b..58c45cd02f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,13 @@ FROM python:2.7-onbuild +ARG timezone=Etc/UTC +RUN echo $timezone > /etc/timezone \ + && ln -sfn /usr/share/zoneinfo/$timezone /etc/localtime \ + && dpkg-reconfigure -f noninteractive tzdata + RUN apt-get update \ && apt-get install -y python-protobuf +VOLUME ["/usr/src/app/web"] + ENTRYPOINT ["python", "pokecli.py"] \ No newline at end of file diff --git a/README.md b/README.md index e11526f42e..6fa12d4f8a 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ This bot takes a strong stance against automating gym battles. Botting gyms will All information on [Getting Started](https://github.com/PokemonGoF/PokemonGo-Bot/wiki/Getting-Started) is available in the [Wiki](https://github.com/PokemonGoF/PokemonGo-Bot/wiki/)! - __Installation__ - [Requirements] (https://github.com/PokemonGoF/PokemonGo-Bot/wiki/Installation#requirements-click-each-one-for-install-guide) - - [How to run with Docker](https://github.com/PokemonGoF/PokemonGo-Bot/wiki/Installation#how-to-run-with-docker) + - [How to run with Docker](https://github.com/PokemonGoF/PokemonGo-Bot/wiki/How-to-run-with-Docker) - [Linux] (https://github.com/PokemonGoF/PokemonGo-Bot/wiki/Installation#installation-linux) - [Mac] (https://github.com/PokemonGoF/PokemonGo-Bot/wiki/Installation#installation-mac) - [Windows] (https://github.com/PokemonGoF/PokemonGo-Bot/wiki/Installation#installation-windows) diff --git a/configs/config.json.example b/configs/config.json.example index 1ee5083b9e..d5835af60c 100644 --- a/configs/config.json.example +++ b/configs/config.json.example @@ -8,6 +8,7 @@ "catch_pokemon": true, "forts": { "spin": true, + "move_to_spin": true, "avoid_circles": true, "max_circle_size": 50 }, @@ -56,5 +57,28 @@ "// any": {"keep_best_iv": 3}, "// Example of keeping the 2 strongest (based on CP) and 3 best (based on IV) Zubat:": {}, "// Zubat": {"keep_best_cp": 2, "keep_best_iv": 3} - } + }, + "vips" : { + "Any pokemon put here directly force to use Berry & Best Ball to capture, to secure the capture rate!": {}, + "any": {"catch_above_cp": 1200, "catch_above_iv": 0.9, "logic": "or" }, + "Lapras": {}, + "Moltres": {}, + "Zapdos": {}, + "Articuno": {}, + + "// S-Tier pokemons (if pokemon can be evolved into tier, list the representative)": {}, + "Mewtwo": {}, + "Dragonite": {}, + "Snorlax": {}, + "// Mew evolves to Mewtwo": {}, + "Mew": {}, + "Arcanine": {}, + "Vaporeon": {}, + "Gyarados": {}, + "Exeggutor": {}, + "Muk": {}, + "Weezing": {}, + "Flareon": {} + + } } diff --git a/configs/config.json.path.example b/configs/config.json.path.example new file mode 100644 index 0000000000..f29ba2043f --- /dev/null +++ b/configs/config.json.path.example @@ -0,0 +1,66 @@ +{ + "auth_service": "google", + "username": "YOUR_USERNAME", + "password": "YOUR_PASSWORD", + "location": "SOME_LOCATION", + "gmapkey": "GOOGLE_MAPS_API_KEY", + "max_steps": 5, + "catch_pokemon": true, + "forts": { + "spin": true, + "move_to_spin": false, + "avoid_circles": true, + "max_circle_size": 50 + }, + "navigator": { + "type": "path", + "path_mode": "loop", + "path_file": "configs/path.example.json" + }, + "websocket_server": false, + "walk": 4.16, + "action_wait_min": 1, + "action_wait_max": 4, + "debug": false, + "test": false, + "health_record": true, + "location_cache": true, + "distance_unit": "km", + "reconnecting_timeout": 15, + "item_filter": { + "1": { "keep" : 100 }, + "101": { "keep" : 10 }, + "102": { "keep" : 30 }, + "103": { "keep" : 30 }, + "201": { "keep" : 30 }, + "701": { "keep" : 100 } + }, + "evolve_all": "NONE", + "evolve_speed": 20, + "evolve_cp_min": 300, + "use_lucky_egg": false, + "hatch_eggs": true, + "longer_eggs_first": true, + "evolve_captured": "NONE", + "release_pokemon": true, + "catch_randomize_reticle_factor": 1.0, + "catch_randomize_spin_factor": 1.0, + "catch": { + "any": {"catch_above_cp": 0, "catch_above_iv": 0, "logic": "or"}, + "// Example of always catching Rattata:": {}, + "// Rattata": { "always_catch" : true } + }, + "release": { + "any": {"release_below_cp": 0, "release_below_iv": 0, "logic": "or"}, + "// Example of always releasing Rattata:": {}, + "// Rattata": {"always_release": true}, + "// Example of keeping 3 stronger (based on CP) Pidgey:": {}, + "// Pidgey": {"keep_best_cp": 3}, + "// Example of keeping 2 stronger (based on IV) Zubat:": {}, + "// Zubat": {"keep_best_iv": 2}, + "// Also, it is working with any": {}, + "// any": {"keep_best_iv": 3}, + "// Example of keeping the 2 strongest (based on CP) and 3 best (based on IV) Zubat:": {}, + "// Zubat": {"keep_best_cp": 2, "keep_best_iv": 3} + } +} diff --git a/configs/config.json.pokemon.example b/configs/config.json.pokemon.example index bdbbedaba8..e0e1be7eaf 100644 --- a/configs/config.json.pokemon.example +++ b/configs/config.json.pokemon.example @@ -8,6 +8,7 @@ "catch_pokemon": true, "forts": { "spin": true, + "move_to_spin": true, "avoid_circles": true, "max_circle_size": 50 }, @@ -284,5 +285,28 @@ "Raichu": { "release_below_cp": 708, "release_below_iv": 0.8, "logic": "and" }, "Cloyster": { "release_below_cp": 717, "release_below_iv": 0.8, "logic": "and"}, "Mr. Mime": { "release_below_cp": 650, "release_below_iv": 0.8, "logic": "and" } - } + }, + "vips" : { + "Any pokemon put here directly force to use Berry & Best Ball to capture, to secure the capture rate!": {}, + "any": {"catch_above_cp": 1200, "catch_above_iv": 0.9, "logic": "or" }, + "Lapras": {}, + "Moltres": {}, + "Zapdos": {}, + "Articuno": {}, + + "// S-Tier pokemons (if pokemon can be evolved into tier, list the representative)": {}, + "Mewtwo": {}, + "Dragonite": {}, + "Snorlax": {}, + "// Mew evolves to Mewtwo": {}, + "Mew": {}, + "Arcanine": {}, + "Vaporeon": {}, + "Gyarados": {}, + "Exeggutor": {}, + "Muk": {}, + "Weezing": {}, + "Flareon": {} + + } } diff --git a/configs/path.example.json b/configs/path.example.json new file mode 100644 index 0000000000..4936880587 --- /dev/null +++ b/configs/path.example.json @@ -0,0 +1,6 @@ +[ + {"location": "32.087504, 34.806118"}, + {"location": "Bialik 150, Ramat Gan"}, + {"location": "Ayalon Highway, Ramat Gan"}, + {"location": "32.091280, 34.795261"} +] diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000000..47f362976b --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,20 @@ +version: '2' +services: + bot1-pokego: + build: . + volumes: + - ./configs/config.json:/usr/src/app/configs/config.json + stdin_open: true + tty: true + bot1-pokegoweb: + image: python:2.7 + ports: + - "8000:8000" + volumes_from: + - bot1-pokego + volumes: + - ./configs/userdata.js:/usr/src/app/web/userdata.js + working_dir: /usr/src/app/web + command: bash -c "echo 'Serving HTTP on 0.0.0.0 port 8000' && python -m SimpleHTTPServer > /dev/null 2>&1" + depends_on: + - bot1-pokego \ No newline at end of file diff --git a/pokecli.py b/pokecli.py index 362f02659d..fb903bfea0 100755 --- a/pokecli.py +++ b/pokecli.py @@ -70,32 +70,37 @@ def main(): except KeyboardInterrupt: logger.log('Exiting PokemonGo Bot', 'red') finished = True - if bot.metrics.start_time is None: - return # Bot didn't actually start, no metrics to show. - - metrics = bot.metrics - metrics.capture_stats() - logger.log('') - logger.log('Ran for {}'.format(metrics.runtime()), 'cyan') - logger.log('Total XP Earned: {} Average: {:.2f}/h'.format(metrics.xp_earned(), metrics.xp_per_hour()), 'cyan') - logger.log('Travelled {:.2f}km'.format(metrics.distance_travelled()), 'cyan') - logger.log('Visited {} stops'.format(metrics.visits['latest'] - metrics.visits['start']), 'cyan') - logger.log('Encountered {} pokemon, {} caught, {} released, {} evolved, {} never seen before' - .format(metrics.num_encounters(), metrics.num_captures(), metrics.releases, - metrics.num_evolutions(), metrics.num_new_mons()), 'cyan') - logger.log('Threw {} pokeball{}'.format(metrics.num_throws(), '' if metrics.num_throws() == 1 else 's'), - 'cyan') - logger.log('Earned {} Stardust'.format(metrics.earned_dust()), 'cyan') - logger.log('') - if metrics.highest_cp is not None: - logger.log('Highest CP Pokemon: {}'.format(metrics.highest_cp['desc']), 'cyan') - if metrics.most_perfect is not None: - logger.log('Most Perfect Pokemon: {}'.format(metrics.most_perfect['desc']), 'cyan') - - + report_summary(bot) except NotLoggedInException: logger.log('[x] Error while connecting to the server, please wait %s minutes' % config.reconnecting_timeout, 'red') time.sleep(config.reconnecting_timeout * 60) + except: + # always report session summary and then raise exception + report_summary(bot) + raise + +def report_summary(bot): + if bot.metrics.start_time is None: + return # Bot didn't actually start, no metrics to show. + + metrics = bot.metrics + metrics.capture_stats() + logger.log('') + logger.log('Ran for {}'.format(metrics.runtime()), 'cyan') + logger.log('Total XP Earned: {} Average: {:.2f}/h'.format(metrics.xp_earned(), metrics.xp_per_hour()), 'cyan') + logger.log('Travelled {:.2f}km'.format(metrics.distance_travelled()), 'cyan') + logger.log('Visited {} stops'.format(metrics.visits['latest'] - metrics.visits['start']), 'cyan') + logger.log('Encountered {} pokemon, {} caught, {} released, {} evolved, {} never seen before' + .format(metrics.num_encounters(), metrics.num_captures(), metrics.releases, + metrics.num_evolutions(), metrics.num_new_mons()), 'cyan') + logger.log('Threw {} pokeball{}'.format(metrics.num_throws(), '' if metrics.num_throws() == 1 else 's'), + 'cyan') + logger.log('Earned {} Stardust'.format(metrics.earned_dust()), 'cyan') + logger.log('') + if metrics.highest_cp is not None: + logger.log('Highest CP Pokemon: {}'.format(metrics.highest_cp['desc']), 'cyan') + if metrics.most_perfect is not None: + logger.log('Most Perfect Pokemon: {}'.format(metrics.most_perfect['desc']), 'cyan') def init_config(): parser = argparse.ArgumentParser() @@ -216,6 +221,37 @@ def init_config(): type=int, default=50 ) + + add_config( + parser, + load, + short_flag="-n", + long_flag="--navigator.type", + help="Set the navigator to be used(DEFAULT spiral)", + type=str, + default='spiral' + ) + + add_config( + parser, + load, + short_flag="-pm", + long_flag="--navigator.path_mode", + help="Set the mode for the path navigator (DEFAULT loop)", + type=str, + default="loop" + ) + + add_config( + parser, + load, + short_flag="-pf", + long_flag="--navigator.path_file", + help="Set the file containing the path for the path navigator (GPX or JSON).", + type=str, + default=None + ) + add_config( parser, load, @@ -333,6 +369,15 @@ def init_config(): type=int, default=10, ) + add_config( + parser, + load, + short_flag="-mts", + long_flag="--forts.move_to_spin", + help="Moves to forts nearby ", + type=bool, + default=True + ) add_config( parser, load, @@ -365,6 +410,8 @@ def init_config(): config.hatch_eggs = load.get("hatch_eggs", True) config.longer_eggs_first = load.get("longer_eggs_first", True) + + config.vips = load.get('vips',{}) if config.auth_service not in ['ptc', 'google']: logging.error("Invalid Auth service specified! ('ptc' or 'google')") diff --git a/pokemongo_bot/__init__.py b/pokemongo_bot/__init__.py index 751b7fef16..53ef7ccffa 100644 --- a/pokemongo_bot/__init__.py +++ b/pokemongo_bot/__init__.py @@ -15,6 +15,7 @@ import cell_workers import logger +import navigators from api_wrapper import ApiWrapper from cell_workers.utils import distance from event_manager import EventManager @@ -23,24 +24,10 @@ from metrics import Metrics from pokemongo_bot.event_handlers import LoggingHandler, SocketIoHandler from pokemongo_bot.socketio_server.runner import SocketIoRunner -from spiral_navigator import SpiralNavigator from worker_result import WorkerResult - +from tree_config_builder import ConfigException, TreeConfigBuilder class PokemonGoBot(object): - - WORKERS = [ - cell_workers.SoftBanWorker, - cell_workers.IncubateEggsWorker, - cell_workers.PokemonTransferWorker, - cell_workers.EvolveAllWorker, - cell_workers.RecycleItemsWorker, - cell_workers.CatchVisiblePokemonWorker, - cell_workers.MoveToFortWorker, - cell_workers.CatchLuredPokemonWorker, - cell_workers.SeenFortWorker - ] - @property def position(self): return self.api._position_lat, self.api._position_lng, 0 @@ -48,7 +35,9 @@ def position(self): def __init__(self, config): self.config = config self.fort_timeouts = dict() - self.pokemon_list = json.load(open(os.path.join('data', 'pokemon.json'))) + 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 @@ -58,12 +47,18 @@ def __init__(self, config): self.softban = False # Make our own copy of the workers for this instance - self.workers = list(self.WORKERS) + self.workers = [] def start(self): self._setup_logging() self._setup_api() - self.navigator = SpiralNavigator(self) + self._setup_workers() + + if self.config.navigator_type == 'spiral': + self.navigator=navigators.SpiralNavigator(self) + elif self.config.navigator_type == 'path': + self.navigator=navigators.PathNavigator(self) + random.seed() def _setup_event_system(self): @@ -86,13 +81,13 @@ def _setup_event_system(self): def tick(self): self.cell = self.get_meta_cell() - self.tick_count +=1 + self.tick_count += 1 # Check if session token has expired self.check_session(self.position[0:2]) for worker in self.workers: - if worker(self).work() == WorkerResult.RUNNING: + if worker.work() == WorkerResult.RUNNING: return self.navigator.take_step() @@ -120,12 +115,13 @@ def get_meta_cell(self): } 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 == 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 == None: + if lng is None: lng = self.api._position_lng - if alt == None: + if alt is None: alt = 0 if cells == []: @@ -138,7 +134,9 @@ def update_web_location(self, cells=[], lat=None, lng=None, alt=None): cell_id=cellid ) response_dict = self.api.call() - map_objects = response_dict.get('responses', {}).get('GET_MAP_OBJECTS', {}) + map_objects = response_dict.get( + 'responses', {} + ).get('GET_MAP_OBJECTS', {}) status = map_objects.get('status', None) cells = map_objects['map_cells'] @@ -147,39 +145,46 @@ def update_web_location(self, cells=[], lat=None, lng=None, alt=None): if 'forts' in cell: for fort in cell['forts']: if fort.get('type') != 1: - 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')) + 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') + ) response_gym_details = self.api.call() - fort['gym_details'] = response_gym_details.get('responses', {}).get('GET_GYM_DETAILS', None) + fort['gym_details'] = response_gym_details.get( + 'responses', {} + ).get('GET_GYM_DETAILS', None) - user_data_cells = "data/cells-%s.json" % (self.config.username) + 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)) + 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, + json.dump({ + 'lat': lat, 'lng': lng, 'alt': alt, 'cells': cells - }, outfile) + }, 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)) + 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}, 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) @@ -191,7 +196,9 @@ def find_close_cells(self, lat, lng): cell_id=cellid ) response_dict = self.api.call() - map_objects = response_dict.get('responses', {}).get('GET_MAP_OBJECTS', {}) + map_objects = response_dict.get( + 'responses', {} + ).get('GET_MAP_OBJECTS', {}) status = map_objects.get('status', None) map_cells = [] @@ -235,12 +242,27 @@ def _setup_logging(self): def check_session(self, position): # Check session expiry if self.api._auth_provider and self.api._auth_provider._ticket_expire: - remaining_time = self.api._auth_provider._ticket_expire/1000 - time.time() + + # 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') 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') self.api.reset_auth() @@ -248,8 +270,8 @@ def login(self): self.api.set_position(lat, lng, 0) while not self.api.login(self.config.auth_service, - str(self.config.username), - str(self.config.password)): + 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') @@ -275,12 +297,26 @@ def _setup_api(self): # send empty map_cells and then our position self.update_web_location() + def _setup_workers(self): + self.workers = [ + cell_workers.HandleSoftBan(self), + cell_workers.IncubateEggs(self), + cell_workers.TransferPokemon(self), + cell_workers.EvolveAll(self), + cell_workers.RecycleItems(self), + cell_workers.CatchVisiblePokemon(self), + cell_workers.SpinFort(self), + cell_workers.MoveToFort(self), + cell_workers.CatchLuredPokemon(self), + cell_workers.SpinFort(self) + ] + def _print_character_info(self): # get player profile call # ---------------------- self.api.get_player() response_dict = self.api.call() - #print('Response dictionary: \n\r{}'.format(json.dumps(response_dict, indent=2))) + # print('Response dictionary: \n\r{}'.format(json.dumps(response_dict, indent=2))) currency_1 = "0" currency_2 = "0" @@ -288,7 +324,10 @@ def _print_character_info(self): 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') + logger.log( + "The API didn't return player info, servers are unstable - " + "retrying.", 'red' + ) sleep(5) self._print_character_info() @@ -308,26 +347,50 @@ def _print_character_info(self): 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') + 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]) + + 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]) + + + 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]) + + + 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]) + + + 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]) + + + 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]) + + + logger.log( + 'Revive: ' + str(items_stock[201]) + ' | MaxRevive: ' + str(items_stock[202]), 'cyan') logger.log('') @@ -372,13 +435,14 @@ def current_inventory(self): inventory_dict = inventory_req['responses']['GET_INVENTORY'][ 'inventory_delta']['inventory_items'] - user_web_inventory = 'web/inventory-%s.json' % (self.config.username) + 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)} + items_stock = {x.value: 0 for x in list(Item)} for item in inventory_dict: try: @@ -434,7 +498,7 @@ def _set_starting_position(self): if self.config.location: location_str = self.config.location.encode('utf-8') - location = (self._get_pos_by_name(location_str.replace(" ", ""))) + location = (self.get_pos_by_name(location_str.replace(" ", ""))) self.api.set_position(*location) logger.log('') logger.log(u'Location Found: {}'.format(self.config.location)) @@ -444,43 +508,58 @@ def _set_starting_position(self): 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: + self.config.username) as f: location_json = json.load(f) - location = (location_json['lat'], - location_json['lng'], 0.0) - #print(location) + location = ( + location_json['lat'], + location_json['lng'], + 0.0 + ) + # print(location) self.api.set_position(*location) logger.log('') logger.log( - '[x] Last location flag used. Overriding passed in location') + '[x] Last location flag used. Overriding passed in location' + ) logger.log( '[x] Last in-game location was set as: {}'.format( - self.position)) + self.position + ) + ) logger.log('') has_position = True return except Exception: - if(has_position == False): + 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...') + "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): + 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{6,7}", location_name) + possible_coordinates = re.findall( + "[-]?\d{1,3}[.]\d{6,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. + # 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") + '[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) @@ -489,13 +568,13 @@ def _get_pos_by_name(self, location_name): def heartbeat(self): # Remove forts that we can now spin again. - self.fort_timeouts = {id: timeout for id ,timeout + self.fort_timeouts = {id: timeout for id, timeout in self.fort_timeouts.iteritems() if timeout >= time.time() * 1000} self.api.get_player() self.api.check_awarded_badges() self.api.call() - self.update_web_location() # updates every tick + self.update_web_location() # updates every tick def get_inventory_count(self, what): response_dict = self.get_inventory() @@ -510,11 +589,11 @@ def get_inventory_count(self, what): for item in response_dict['responses'][ 'GET_INVENTORY']['inventory_delta'][ 'inventory_items']: - #print('item {}'.format(item)) + # print('item {}'.format(item)) if 'inventory_item_data' in item: if 'pokemon_data' in item[ 'inventory_item_data']: - pokecount = pokecount + 1 + pokecount += 1 if 'item' in item['inventory_item_data']: if 'count' in item['inventory_item_data'][ 'item']: @@ -553,20 +632,30 @@ def get_player_info(self): if 'level' in playerdata: if 'experience' in playerdata: - logger.log('Level: {level}'.format(**playerdata) + - ' (Next Level: {} XP)'.format(nextlvlxp) + - ' (Total: {experience} XP)'.format(**playerdata), 'cyan') - + logger.log( + 'Level: {level}'.format( + **playerdata) + + ' (Next Level: {} XP)'.format( + nextlvlxp) + + ' (Total: {experience} XP)' + ''.format(**playerdata), 'cyan') if 'pokemons_captured' in playerdata: if 'poke_stop_visits' in playerdata: logger.log( - 'Pokemon Captured: {pokemons_captured}'.format(**playerdata) + - ' | Pokestops Visited: {poke_stop_visits}'.format(**playerdata), 'cyan') + '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 + enough_space = ( + self.get_inventory_count('item') < + self._player['max_item_storage'] - number_of_things_gained_by_stop + ) return enough_space diff --git a/pokemongo_bot/cell_workers/__init__.py b/pokemongo_bot/cell_workers/__init__.py index 4aaa8ee52e..a0464168c6 100644 --- a/pokemongo_bot/cell_workers/__init__.py +++ b/pokemongo_bot/cell_workers/__init__.py @@ -1,12 +1,12 @@ # -*- coding: utf-8 -*- -from catch_lured_pokemon_worker import CatchLuredPokemonWorker -from catch_visible_pokemon_worker import CatchVisiblePokemonWorker -from evolve_all_worker import EvolveAllWorker -from incubate_eggs_worker import IncubateEggsWorker -from move_to_fort_worker import MoveToFortWorker +from catch_lured_pokemon import CatchLuredPokemon +from catch_visible_pokemon import CatchVisiblePokemon +from evolve_all import EvolveAll +from incubate_eggs import IncubateEggs +from move_to_fort import MoveToFort from pokemon_catch_worker import PokemonCatchWorker -from pokemon_transfer_worker import PokemonTransferWorker -from recycle_items_worker import RecycleItemsWorker -from seen_fort_worker import SeenFortWorker -from soft_ban_worker import SoftBanWorker +from transfer_pokemon import TransferPokemon +from recycle_items import RecycleItems +from spin_fort import SpinFort +from handle_soft_ban import HandleSoftBan diff --git a/pokemongo_bot/cell_workers/catch_lured_pokemon_worker.py b/pokemongo_bot/cell_workers/catch_lured_pokemon.py similarity index 71% rename from pokemongo_bot/cell_workers/catch_lured_pokemon_worker.py rename to pokemongo_bot/cell_workers/catch_lured_pokemon.py index ad102ed51f..f4996a1274 100644 --- a/pokemongo_bot/cell_workers/catch_lured_pokemon_worker.py +++ b/pokemongo_bot/cell_workers/catch_lured_pokemon.py @@ -1,17 +1,14 @@ from pokemongo_bot import logger +from pokemongo_bot.cell_workers.utils import fort_details from pokemongo_bot.cell_workers.pokemon_catch_worker import PokemonCatchWorker -class CatchLuredPokemonWorker(object): +class CatchLuredPokemon(object): def __init__(self, bot): self.bot = bot - self.cell = bot.cell - self.api = bot.api - self.config = bot.config - self.position = bot.position def work(self): - if not self.config.catch_pokemon: + if not self.bot.config.catch_pokemon: return lured_pokemon = self.get_lured_pokemon() @@ -25,14 +22,10 @@ def get_lured_pokemon(self): return False fort = forts[0] - - self.api.fort_details(fort_id=fort['id'], + details = fort_details(self.bot, fort_id=fort['id'], latitude=fort['latitude'], longitude=fort['longitude']) - - response_dict = self.api.call() - fort_details = response_dict.get('responses', {}).get('FORT_DETAILS', {}) - fort_name = fort_details.get('name', 'Unknown').encode('utf8', 'replace') + fort_name = details.get('name', 'Unknown').encode('utf8', 'replace') encounter_id = fort.get('lure_info', {}).get('encounter_id', None) diff --git a/pokemongo_bot/cell_workers/catch_visible_pokemon.py b/pokemongo_bot/cell_workers/catch_visible_pokemon.py new file mode 100644 index 0000000000..06daa2e2cb --- /dev/null +++ b/pokemongo_bot/cell_workers/catch_visible_pokemon.py @@ -0,0 +1,43 @@ +import json + +from pokemongo_bot import logger +from pokemongo_bot.cell_workers.pokemon_catch_worker import PokemonCatchWorker +from utils import distance + + +class CatchVisiblePokemon(object): + def __init__(self, bot): + self.bot = bot + + def work(self): + if not self.bot.config.catch_pokemon: + return + + if 'catchable_pokemons' in self.bot.cell and len(self.bot.cell['catchable_pokemons']) > 0: + logger.log('Something rustles nearby!') + # Sort all by distance from current pos- eventually this should + # build graph & A* it + self.bot.cell['catchable_pokemons'].sort( + key= + lambda x: distance(self.bot.position[0], self.bot.position[1], x['latitude'], x['longitude'])) + + user_web_catchable = 'web/catchable-%s.json' % self.bot.config.username + for pokemon in self.bot.cell['catchable_pokemons']: + with open(user_web_catchable, 'w') as outfile: + json.dump(pokemon, outfile) + + return self.catch_pokemon(self.bot.cell['catchable_pokemons'][0]) + + if 'wild_pokemons' in self.bot.cell and len(self.bot.cell['wild_pokemons']) > 0: + # Sort all by distance from current pos- eventually this should + # build graph & A* it + self.bot.cell['wild_pokemons'].sort( + key= + lambda x: distance(self.bot.position[0], self.bot.position[1], x['latitude'], x['longitude'])) + return self.catch_pokemon(self.bot.cell['wild_pokemons'][0]) + + def catch_pokemon(self, pokemon): + worker = PokemonCatchWorker(pokemon, self.bot) + return_value = worker.work() + + return return_value diff --git a/pokemongo_bot/cell_workers/catch_visible_pokemon_worker.py b/pokemongo_bot/cell_workers/catch_visible_pokemon_worker.py deleted file mode 100644 index 29d114cb20..0000000000 --- a/pokemongo_bot/cell_workers/catch_visible_pokemon_worker.py +++ /dev/null @@ -1,47 +0,0 @@ -import json - -from pokemongo_bot import logger -from pokemongo_bot.cell_workers.pokemon_catch_worker import PokemonCatchWorker -from utils import distance - - -class CatchVisiblePokemonWorker(object): - def __init__(self, bot): - self.bot = bot - self.cell = bot.cell - self.api = bot.api - self.config = bot.config - self.position = bot.position - - def work(self): - if not self.config.catch_pokemon: - return - - if 'catchable_pokemons' in self.cell and len(self.cell['catchable_pokemons']) > 0: - logger.log('Something rustles nearby!') - # Sort all by distance from current pos- eventually this should - # build graph & A* it - self.cell['catchable_pokemons'].sort( - key= - lambda x: distance(self.position[0], self.position[1], x['latitude'], x['longitude'])) - - user_web_catchable = 'web/catchable-%s.json' % self.config.username - for pokemon in self.cell['catchable_pokemons']: - with open(user_web_catchable, 'w') as outfile: - json.dump(pokemon, outfile) - - return self.catch_pokemon(self.cell['catchable_pokemons'][0]) - - if 'wild_pokemons' in self.cell and len(self.cell['wild_pokemons']) > 0: - # Sort all by distance from current pos- eventually this should - # build graph & A* it - self.cell['wild_pokemons'].sort( - key= - lambda x: distance(self.position[0], self.position[1], x['latitude'], x['longitude'])) - return self.catch_pokemon(self.cell['wild_pokemons'][0]) - - def catch_pokemon(self, pokemon): - worker = PokemonCatchWorker(pokemon, self.bot) - return_value = worker.work() - - return return_value diff --git a/pokemongo_bot/cell_workers/evolve_all_worker.py b/pokemongo_bot/cell_workers/evolve_all.py similarity index 94% rename from pokemongo_bot/cell_workers/evolve_all_worker.py rename to pokemongo_bot/cell_workers/evolve_all.py index a41b3d92ba..0d8dd4d7a1 100644 --- a/pokemongo_bot/cell_workers/evolve_all_worker.py +++ b/pokemongo_bot/cell_workers/evolve_all.py @@ -3,12 +3,10 @@ from pokemongo_bot.item_list import Item -class EvolveAllWorker(object): +class EvolveAll(object): def __init__(self, bot): self.api = bot.api - self.config = bot.config self.bot = bot - # self.position = bot.position def work(self): if not self._should_run(): @@ -25,9 +23,9 @@ def work(self): else: evolve_list = self._sort_by_cp_iv( response_dict['responses']['GET_INVENTORY']['inventory_delta']['inventory_items']) - if self.config.evolve_all[0] != 'all': + if self.bot.config.evolve_all[0] != 'all': # filter out non-listed pokemons - evolve_list = [x for x in evolve_list if str(x[1]) in self.config.evolve_all] + evolve_list = [x for x in evolve_list if str(x[1]) in self.bot.config.evolve_all] # enable to limit number of pokemons to evolve. Useful for testing. # nn = 3 @@ -52,11 +50,11 @@ def work(self): def _should_run(self): # Will skip evolving if user wants to use an egg and there is none - if not self.config.evolve_all: + if not self.bot.config.evolve_all: return False # Evolve all is used - Don't run after the first tick or if the config flag is false - if self.bot.tick_count is not 0 or not self.config.use_lucky_egg: + if self.bot.tick_count is not 1 or not self.bot.config.use_lucky_egg: return True lucky_egg_count = self.bot.item_inventory_count(Item.ITEM_LUCKY_EGG.value) @@ -128,7 +126,7 @@ def _sort_by_cp_iv(self, inventory_items): pokemon['cp'], self._compute_iv(pokemon) ] - if pokemon['cp'] > self.config.evolve_cp_min: + if pokemon['cp'] > self.bot.config.evolve_cp_min: pokemons1.append(v) else: pokemons2.append(v) @@ -160,8 +158,8 @@ def _execute_pokemon_evolve(self, pokemon, cache): pokemon_name, pokemon_cp, pokemon_iv )) - if self.config.evolve_speed: - sleep(self.config.evolve_speed) + if self.bot.config.evolve_speed: + sleep(self.bot.config.evolve_speed) else: sleep(3.7) @@ -245,13 +243,13 @@ def should_release_pokemon(self, pokemon_name, cp, iv): return logic_to_function[cp_iv_logic](*release_results.values()) def _get_release_config_for(self, pokemon): - release_config = self.config.release.get(pokemon) + release_config = self.bot.config.release.get(pokemon) if not release_config: - release_config = self.config.release['any'] + release_config = self.bot.config.release['any'] return release_config def _get_exceptions(self): - exceptions = self.config.release.get('exceptions') + exceptions = self.bot.config.release.get('exceptions') if not exceptions: return None return exceptions diff --git a/pokemongo_bot/cell_workers/soft_ban_worker.py b/pokemongo_bot/cell_workers/handle_soft_ban.py similarity index 85% rename from pokemongo_bot/cell_workers/soft_ban_worker.py rename to pokemongo_bot/cell_workers/handle_soft_ban.py index 513a871c8b..485fc58f6d 100644 --- a/pokemongo_bot/cell_workers/soft_ban_worker.py +++ b/pokemongo_bot/cell_workers/handle_soft_ban.py @@ -2,17 +2,15 @@ from pokemongo_bot import logger from pokemongo_bot.constants import Constants -from pokemongo_bot.cell_workers import MoveToFortWorker, SeenFortWorker +from pokemongo_bot.cell_workers import MoveToFort from pokemongo_bot.cell_workers.utils import distance from pokemongo_bot.worker_result import WorkerResult -class SoftBanWorker(object): +class HandleSoftBan(object): def __init__(self, bot): self.bot = bot - self.api = bot.api - self.config = bot.config def work(self): if not self.should_run(): @@ -33,7 +31,7 @@ def work(self): ) if fort_distance > Constants.MAX_DISTANCE_FORT_IS_REACHABLE: - MoveToFortWorker(self.bot).work() + MoveToFort(self.bot).work() self.bot.recent_forts = self.bot.recent_forts[0:-1] if forts[0]['id'] in self.bot.fort_timeouts: del self.bot.fort_timeouts[forts[0]['id']] @@ -44,18 +42,18 @@ def work(self): if (i + 1) % 10 == 0: logger.log('Spin #{}'.format(str(i+1))) self.spin_fort(forts[0]) - self.softban = False + self.bot.softban = False logger.log('Softban should be fixed.') def spin_fort(self, fort): - self.api.fort_search( + self.bot.api.fort_search( fort_id=fort['id'], fort_latitude=fort['latitude'], fort_longitude=fort['longitude'], player_latitude=f2i(self.bot.position[0]), player_longitude=f2i(self.bot.position[1]) ) - self.api.call() + self.bot.api.call() def should_run(self): return self.bot.config.softban_fix and self.bot.softban diff --git a/pokemongo_bot/cell_workers/incubate_eggs_worker.py b/pokemongo_bot/cell_workers/incubate_eggs.py similarity index 73% rename from pokemongo_bot/cell_workers/incubate_eggs_worker.py rename to pokemongo_bot/cell_workers/incubate_eggs.py index dd3654b510..dfced055a7 100644 --- a/pokemongo_bot/cell_workers/incubate_eggs_worker.py +++ b/pokemongo_bot/cell_workers/incubate_eggs.py @@ -2,7 +2,7 @@ from pokemongo_bot.human_behaviour import sleep -class IncubateEggsWorker(object): +class IncubateEggs(object): last_km_walked = 0 def __init__(self, bot): @@ -12,6 +12,7 @@ def __init__(self, bot): self.eggs = [] self.km_walked = 0 self.hatching_animation_delay = 4.20 + self.max_iv = 45.0 def work(self): if not self.bot.config.hatch_eggs: @@ -22,14 +23,14 @@ def work(self): except: return - if self.used_incubators and IncubateEggsWorker.last_km_walked != self.km_walked: + if self.used_incubators and IncubateEggs.last_km_walked != self.km_walked: self.used_incubators.sort(key=lambda x: x.get("km")) km_left = self.used_incubators[0]['km']-self.km_walked if km_left <= 0: self._hatch_eggs() else: logger.log('[x] Next egg incubates in {:.2f} km'.format(km_left),'yellow') - IncubateEggsWorker.last_km_walked = self.km_walked + IncubateEggs.last_km_walked = self.km_walked sorting = self.bot.config.longer_eggs_first self.eggs.sort(key=lambda x: x.get("km"), reverse=sorting) @@ -67,6 +68,9 @@ def _check_inventory(self, lookup_ids=[]): inv = {} response_dict = self.bot.get_inventory() matched_pokemon = [] + temp_eggs = [] + temp_used_incubators = [] + temp_ready_incubators = [] inv = reduce( dict.__getitem__, ["responses", "GET_INVENTORY", "inventory_delta", "inventory_items"], @@ -75,24 +79,26 @@ def _check_inventory(self, lookup_ids=[]): for inv_data in inv: inv_data = inv_data.get("inventory_item_data", {}) if "egg_incubators" in inv_data: + temp_used_incubators = [] + temp_ready_incubators = [] incubators = inv_data.get("egg_incubators", {}).get("egg_incubator",[]) if isinstance(incubators, basestring): # checking for old response incubators = [incubators] for incubator in incubators: if 'pokemon_id' in incubator: - self.used_incubators.append({ + temp_used_incubators.append({ "id": incubator.get('id', -1), "km": incubator.get('target_km_walked', 9001) }) else: - self.ready_incubators.append({ + temp_ready_incubators.append({ "id": incubator.get('id', -1) }) continue if "pokemon_data" in inv_data: pokemon = inv_data.get("pokemon_data", {}) if pokemon.get("is_egg", False) and "egg_incubator_id" not in pokemon: - self.eggs.append({ + temp_eggs.append({ "id": pokemon.get("id", -1), "km": pokemon.get("egg_km_walked_target", -1), "used": False @@ -100,16 +106,22 @@ def _check_inventory(self, lookup_ids=[]): elif 'is_egg' not in pokemon and pokemon['id'] in lookup_ids: matched_pokemon.append({ "pokemon_id": pokemon.get('pokemon_id', -1), - "cp": pokemon.get('cp', -1), + "cp": pokemon.get('cp', 0), "iv": [ - pokemon.get('individual_attack', -1), - pokemon.get('individual_stamina', -1), - pokemon.get('individual_defense', -1) + pokemon.get('individual_attack', 0), + pokemon.get('individual_defense', 0), + pokemon.get('individual_stamina', 0) ] }) continue if "player_stats" in inv_data: self.km_walked = inv_data.get("player_stats", {}).get("km_walked", 0) + if temp_used_incubators: + self.used_incubators = temp_used_incubators + if temp_ready_incubators: + self.ready_incubators = temp_ready_incubators + if temp_eggs: + self.eggs = temp_eggs return matched_pokemon def _hatch_eggs(self): @@ -120,27 +132,34 @@ def _hatch_eggs(self): result = reduce(dict.__getitem__, ["responses", "GET_HATCHED_EGGS"], response_dict) except KeyError: return + pokemon_ids = [] if 'pokemon_id' in result: pokemon_ids = [id for id in result['pokemon_id']] - stardust = result.get('stardust_awarded', 0) - candy = result.get('candy_awarded', 0) - xp = result.get('experience_awarded', 0) + stardust = result.get('stardust_awarded', "error") + candy = result.get('candy_awarded', "error") + xp = result.get('experience_awarded', "error") sleep(self.hatching_animation_delay) self.bot.latest_inventory = None try: pokemon_data = self._check_inventory(pokemon_ids) + for pokemon in pokemon_data: + # pokemon ids seem to be offset by one + if pokemon['pokemon_id']!=-1: + pokemon['name'] = self.bot.pokemon_list[(pokemon['pokemon_id']-1)]['Name'] + else: + pokemon['name'] = "error" except: - pass # just proceed with what we have - for pokemon in pokemon_data: - # pokemon ids seem to be offset by one - pokemon['name'] = self.bot.pokemon_list[(pokemon['pokemon_id']-1)]['Name'] + pokemon_data = [{"name":"error","cp":"error","iv":"error"}] logger.log("-"*30, log_color) + if not pokemon_ids or pokemon_data[0]['name'] == "error": + logger.log("[!] Eggs hatched, but we had trouble with the response. Please check your inventory to find your new pokemon!",'red') + return logger.log("[!] {} eggs hatched! Received:".format(len(pokemon_data)), log_color) for i in range(len(pokemon_data)): logger.log("-"*30,log_color) logger.log("[!] Pokemon: {}".format(pokemon_data[i]['name']), log_color) logger.log("[!] CP: {}".format(pokemon_data[i]['cp']), log_color) - logger.log("[!] IV: {}".format("/".join(map(str, pokemon_data[i]['iv']))), log_color) + logger.log("[!] IV: {} ({:.2f})".format("/".join(map(str, pokemon_data[i]['iv'])),(sum(pokemon_data[i]['iv'])/self.max_iv)), log_color) logger.log("[!] XP: {}".format(xp[i]), log_color) logger.log("[!] Stardust: {}".format(stardust[i]), log_color) logger.log("[!] Candy: {}".format(candy[i]), log_color) diff --git a/pokemongo_bot/cell_workers/move_to_fort_worker.py b/pokemongo_bot/cell_workers/move_to_fort.py similarity index 62% rename from pokemongo_bot/cell_workers/move_to_fort_worker.py rename to pokemongo_bot/cell_workers/move_to_fort.py index e49d9da52b..6e5fa31940 100644 --- a/pokemongo_bot/cell_workers/move_to_fort_worker.py +++ b/pokemongo_bot/cell_workers/move_to_fort.py @@ -2,22 +2,18 @@ from pokemongo_bot.constants import Constants from pokemongo_bot.step_walker import StepWalker from pokemongo_bot.worker_result import WorkerResult -from utils import distance, format_dist +from utils import distance, format_dist, fort_details -class MoveToFortWorker(object): +class MoveToFort(object): def __init__(self, bot): self.bot = bot - self.api = bot.api - self.config = bot.config - self.fort_timeouts = bot.fort_timeouts - self.recent_forts = bot.recent_forts - self.navigator = bot.navigator - self.position = bot.position def should_run(self): - return self.config.forts_spin and self.bot.has_space_for_loot() + return (self.bot.config.forts_spin and \ + self.bot.config.forts_move_to_spin and \ + self.bot.has_space_for_loot()) or self.bot.softban def work(self): if not self.should_run(): @@ -31,21 +27,24 @@ def work(self): lat = nearest_fort['latitude'] lng = nearest_fort['longitude'] fortID = nearest_fort['id'] - unit = self.config.distance_unit # Unit to use when printing formatted distance + details = fort_details(self.bot, fortID, lat, lng) + fort_name = details.get('name', 'Unknown').encode('utf8', 'replace') + + unit = self.bot.config.distance_unit # Unit to use when printing formatted distance dist = distance( - self.position[0], - self.position[1], + self.bot.position[0], + self.bot.position[1], lat, lng ) if dist > Constants.MAX_DISTANCE_FORT_IS_REACHABLE: - logger.log('Moving towards fort {}, {} left'.format(fortID, format_dist(dist, unit))) + logger.log('Moving towards fort {}, {} left'.format(fort_name, format_dist(dist, unit))) step_walker = StepWalker( self.bot, - self.config.walk, + self.bot.config.walk, lat, lng ) @@ -60,11 +59,11 @@ def get_nearest_fort(self): forts = self.bot.get_forts(order_by_distance=True) # Remove stops that are still on timeout - forts = filter(lambda x: x["id"] not in self.fort_timeouts, forts) + forts = filter(lambda x: x["id"] not in self.bot.fort_timeouts, forts) # Remove all forts which were spun in the last ticks to avoid circles if set - if self.config.forts_avoid_circles: - forts = filter(lambda x: x["id"] not in self.recent_forts, forts) + if self.bot.config.forts_avoid_circles: + forts = filter(lambda x: x["id"] not in self.bot.recent_forts, forts) if len(forts) > 0: return forts[0] diff --git a/pokemongo_bot/cell_workers/pokemon_catch_worker.py b/pokemongo_bot/cell_workers/pokemon_catch_worker.py index b2f57b5032..10c04f1f2c 100644 --- a/pokemongo_bot/cell_workers/pokemon_catch_worker.py +++ b/pokemongo_bot/cell_workers/pokemon_catch_worker.py @@ -76,78 +76,127 @@ def work(self): # logger.log('[x] Rule prevents capture.') return False + flag_VIP = False + # @TODO, use the best ball in stock to catch VIP (Very Important Pokemon: Configurable) + if self.check_vip_pokemon(pokemon_name, cp, pokemon_potential): + logger.log('[-] {} is a VIP Pokemon! [CP {}] [Potential {}] Nice! Try our best to catch it!'.format(pokemon_name, cp, pokemon_potential),'red') + flag_VIP=True + items_stock = self.bot.current_inventory() + berry_id = 701 # @ TODO: use better berries if possible + berries_count = self.bot.item_inventory_count(berry_id) while True: # pick the most simple ball from stock pokeball = 1 # start from 1 - PokeBalls + berry_used = False + + if flag_VIP: + if(berries_count>0): + success_percentage = '{0:.2f}'.format(catch_rate[pokeball-1]*100) + logger.log('Catch Rate with normal Pokeball is low ({}%). Thinking to throw a {}... ({} left!)'.format(success_percentage,self.item_list[str(berry_id)],berries_count-1)) + # Out of all pokeballs! Let's don't waste berry. + if items_stock[1] == 0 and items_stock[2] == 0 and items_stock[3] == 0: + break + + # Use the berry to catch + self.api.use_item_capture(item_id = berry_id,encounter_id = encounter_id,spawn_point_id = self.spawn_point_guid) + response_dict = self.api.call() + if response_dict and response_dict['status_code'] is 1 and 'item_capture_mult' in response_dict['responses']['USE_ITEM_CAPTURE']: + for i in range(len(catch_rate)): + if 'item_capture_mult' in response_dict['responses']['USE_ITEM_CAPTURE']: + catch_rate[i] = catch_rate[i] * response_dict['responses']['USE_ITEM_CAPTURE']['item_capture_mult'] + success_percentage = '{0:.2f}'.format(catch_rate[pokeball-1]*100) + berries_count = berries_count -1 + berry_used = True + logger.log('Threw a berry! Catch Rate with normal Pokeball has increased to {}%'.format(success_percentage)) + else: + if response_dict['status_code'] is 1: + logger.log('Fail to use berry. Seem like you are softbanned.', 'red') + else: + logger.log('Fail to use berry. Status Code: {}'.format(response_dict['status_code']),'red') + + #use the best ball to catch + current_type = pokeball + #debug use normal ball + while current_type < 3: + current_type += 1 + if catch_rate[pokeball-1] < 0.9 and items_stock[current_type] > 0: + # if current ball chance to catch is under 90%, and player has better ball - then use it + pokeball = current_type # use better ball + else: + # If we have a lot of berries (than the great ball), we prefer use a berry first! + if catch_rate[pokeball-1] < 0.42 and items_stock[pokeball+1]+30 < berries_count: + # If it's not the VIP type, we don't want to waste our ultra ball if no balls left. + if items_stock[1] == 0 and items_stock[2] == 0: + break + + success_percentage = '{0:.2f}'.format(catch_rate[pokeball-1]*100) + logger.log('Catch Rate with normal Pokeball is low ({}%). Thinking to throw a {}... ({} left!)'.format(success_percentage,self.item_list[str(berry_id)],berries_count-1)) + self.api.use_item_capture(item_id = berry_id,encounter_id = encounter_id,spawn_point_id = self.spawn_point_guid) + response_dict = self.api.call() + if response_dict and response_dict['status_code'] is 1 and 'item_capture_mult' in response_dict['responses']['USE_ITEM_CAPTURE']: + for i in range(len(catch_rate)): + if 'item_capture_mult' in response_dict['responses']['USE_ITEM_CAPTURE']: + catch_rate[i] = catch_rate[i] * response_dict['responses']['USE_ITEM_CAPTURE']['item_capture_mult'] + success_percentage = '{0:.2f}'.format(catch_rate[pokeball-1]*100) + berries_count = berries_count -1 + berry_used = True + logger.log('Threw a berry! Catch Rate with normal Pokeball has increased to {}%'.format(success_percentage)) + else: + if response_dict['status_code'] is 1: + logger.log('Fail to use berry. Seem like you are softbanned.', 'red') + else: + logger.log('Fail to use berry. Status Code: {}'.format(response_dict['status_code']),'red') - current_type = pokeball - # if this type's stock = 0 and not top tier yet - while items_stock[current_type] is 0 and current_type < 3: - # progress to next tier - current_type += 1 - # next tier's stock > 0 - if items_stock[current_type] > 0: - pokeball = current_type - - # re-check stock again - if items_stock[pokeball] is 0: - logger.log('Out of pokeballs', 'red') - return PokemonCatchWorker.NO_POKEBALLS - - # Use berry to increase success chance. - berry_id = 701 # @ TODO: use better berries if possible - berries_count = self.bot.item_inventory_count(berry_id) - if catch_rate[pokeball-1] < 0.5 and berries_count > 0: # and berry is in stock - success_percentage = '{0:.2f}'.format(catch_rate[pokeball-1]*100) - logger.log( - 'Catch Rate with normal Pokeball is low ({}%). ' - 'Throwing {}... ({} left!)'.format( - success_percentage, - self.item_list[str(berry_id)],berries_count-1 - ) - ) - - if items_stock[pokeball] is 0: - break - - self.api.use_item_capture( - item_id=berry_id, - encounter_id=encounter_id, - spawn_point_id=self.spawn_point_guid - ) - response_dict = self.api.call() - if response_dict and response_dict['status_code'] is 1 and 'item_capture_mult' in \ - response_dict['responses']['USE_ITEM_CAPTURE']: - - for i in range(len(catch_rate)): - if 'item_capture_mult' in response_dict['responses']['USE_ITEM_CAPTURE']: - catch_rate[i] = catch_rate[i] * \ - response_dict['responses']['USE_ITEM_CAPTURE'][ - 'item_capture_mult'] - - success_percentage = '{0:.2f}'.format(catch_rate[pokeball - 1] * 100) - logger.log('Catch Rate with normal Pokeball has increased to {}%'.format( - success_percentage)) else: - if response_dict['status_code'] is 1: - logger.log('Fail to use berry. Seem like you are softbanned.', 'red') - self.bot.softban = True + #We don't have many berry to waste, pick a good ball first. Save some berry for future VIP pokemon + current_type = pokeball + while current_type < 2: + current_type += 1 + if catch_rate[pokeball-1] < 0.35 and items_stock[current_type] > 0: + # if current ball chance to catch is under 35%, and player has better ball - then use it + pokeball = current_type # use better ball + + #if the rate is still low and we didn't throw a berry before use berry + if catch_rate[pokeball-1] < 0.35 and berries_count > 0 and berry_used == False: + # If it's not the VIP type, we don't want to waste our ultra ball if no balls left. + if items_stock[1] == 0 and items_stock[2] == 0: + break + + success_percentage = '{0:.2f}'.format(catch_rate[pokeball-1]*100) + logger.log('Catch Rate with normal Pokeball is low ({}%). Thinking to throw a {}... ({} left!)'.format(success_percentage,self.item_list[str(berry_id)],berries_count-1)) + self.api.use_item_capture(item_id = berry_id,encounter_id = encounter_id,spawn_point_id = self.spawn_point_guid) + response_dict = self.api.call() + if response_dict and response_dict['status_code'] is 1 and 'item_capture_mult' in response_dict['responses']['USE_ITEM_CAPTURE']: + for i in range(len(catch_rate)): + if 'item_capture_mult' in response_dict['responses']['USE_ITEM_CAPTURE']: + catch_rate[i] = catch_rate[i] * response_dict['responses']['USE_ITEM_CAPTURE']['item_capture_mult'] + success_percentage = '{0:.2f}'.format(catch_rate[pokeball-1]*100) + berries_count = berries_count -1 + berry_used = True + logger.log('Threw a berry! Catch Rate with normal Pokeball has increased to {}%'.format(success_percentage)) else: - logger.log( - 'Fail to use berry. Status Code: {}'.format(response_dict['status_code']), - 'red') + if response_dict['status_code'] is 1: + logger.log('Fail to use berry. Seem like you are softbanned.', 'red') + else: + logger.log('Fail to use berry. Status Code: {}'.format(response_dict['status_code']),'red') - # change ball to next tier if catch rate is too low - current_type = pokeball - while current_type < 3: - current_type += 1 - if catch_rate[pokeball - 1] < 0.35 and items_stock[current_type] > 0: - # if current ball chance to catch is under 35%, - # and player has better ball - then use it - pokeball = current_type # use better ball + # Re-check if berry is used, find a ball for a good capture rate + current_type=pokeball + while current_type < 2: + current_type += 1 + if catch_rate[pokeball-1] < 0.35 and items_stock[current_type] > 0: + pokeball = current_type # use better ball - # @TODO, use the best ball in stock to catch VIP (Very Important Pokemon: Configurable) + # This is to avoid rare case that a berry has ben throwed <0.42 + # and still picking normal pokeball (out of stock) -> error + if items_stock[1] == 0 and items_stock[2] > 0: + pokeball = 2 + + # Add this logic to avoid Pokeball = 0, Great Ball = 0, Ultra Ball = X + # And this logic saves Ultra Balls if it's a weak trash pokemon + if catch_rate[pokeball-1]<0.30 and items_stock[3]>0: + pokeball = 3 items_stock[pokeball] -= 1 success_percentage = '{0:.2f}'.format(catch_rate[pokeball - 1] * 100) @@ -333,3 +382,31 @@ def create_encounter_api_call(self): player_latitude=player_latitude, player_longitude=player_longitude) return self.api.call() + + def check_vip_pokemon(self,pokemon, cp, iv): + + vip_name = self.config.vips.get(pokemon) + if vip_name == {}: + return True + else: + catch_config = self.config.vips.get("any") + if not catch_config: + return False + cp_iv_logic = catch_config.get('logic', 'or') + catch_results = { + 'cp': False, + 'iv': False, + } + + catch_cp = catch_config.get('catch_above_cp', 0) + if cp > catch_cp: + catch_results['cp'] = True + catch_iv = catch_config.get('catch_above_iv', 0) + if iv > catch_iv: + catch_results['iv'] = True + logic_to_function = { + 'or': lambda x, y: x or y, + 'and': lambda x, y: x and y + } + return logic_to_function[cp_iv_logic](*catch_results.values()) + diff --git a/pokemongo_bot/cell_workers/recycle_items_worker.py b/pokemongo_bot/cell_workers/recycle_items.py similarity index 55% rename from pokemongo_bot/cell_workers/recycle_items_worker.py rename to pokemongo_bot/cell_workers/recycle_items.py index 953a2c8c05..611b7e3d30 100644 --- a/pokemongo_bot/cell_workers/recycle_items_worker.py +++ b/pokemongo_bot/cell_workers/recycle_items.py @@ -1,40 +1,48 @@ from pokemongo_bot import logger -class RecycleItemsWorker(object): +class RecycleItems(object): def __init__(self, bot): self.bot = bot - self.api = bot.api - self.config = bot.config - self.item_list = bot.item_list def work(self): self.bot.latest_inventory = None item_count_dict = self.bot.item_inventory_count('all') for item_id, bag_count in item_count_dict.iteritems(): - item_name = self.item_list[str(item_id)] - id_filter = self.config.item_filter.get(item_name, 0) + item_name = self.bot.item_list[str(item_id)] + id_filter = self.bot.config.item_filter.get(str(item_id), 0) if id_filter is not 0: id_filter_keep = id_filter.get('keep', 20) bag_count = self.bot.item_inventory_count(item_id) - if item_name in self.config.item_filter and bag_count > id_filter_keep: + if str(item_id) in self.bot.config.item_filter and bag_count > id_filter_keep: items_recycle_count = bag_count - id_filter_keep - response_dict_recycle = self.send_recycle_item_request(item_id=item_id, count=items_recycle_count) - result = response_dict_recycle.get('responses', {}).get('RECYCLE_INVENTORY_ITEM', {}).get('result', 0) + + response_dict_recycle = self.send_recycle_item_request( + item_id=item_id, + count=items_recycle_count + ) + + result = response_dict_recycle.get('responses', {}) \ + .get('RECYCLE_INVENTORY_ITEM', {}) \ + .get('result', 0) if result == 1: # Request success - message_template = "-- Discarded {}x {} (keeps only {} maximum) " - message = message_template.format(str(items_recycle_count), item_name, str(id_filter_keep)) + message_template = "-- Recycled {}x {} (keeps only {} maximum) " + message = message_template.format( + str(items_recycle_count), + item_name, + str(id_filter_keep) + ) logger.log(message, 'green') else: - logger.log("-- Failed to discard " + item_name, 'red') + logger.log("-- Failed to recycle " + item_name + "has failed!", 'red') def send_recycle_item_request(self, item_id, count): - self.api.recycle_inventory_item(item_id=item_id, count=count) - inventory_req = self.api.call() + self.bot.api.recycle_inventory_item(item_id=item_id, count=count) + inventory_req = self.bot.api.call() # Example of good request response #{'responses': {'RECYCLE_INVENTORY_ITEM': {'result': 1, 'new_count': 46}}, 'status_code': 1, 'auth_ticket': {'expire_timestamp_ms': 1469306228058L, 'start': '/HycFyfrT4t2yB2Ij+yoi+on778aymMgxY6RQgvrGAfQlNzRuIjpcnDd5dAxmfoTqDQrbz1m2dGqAIhJ+eFapg==', 'end': 'f5NOZ95a843tgzprJo4W7Q=='}, 'request_id': 8145806132888207460L} diff --git a/pokemongo_bot/cell_workers/seen_fort_worker.py b/pokemongo_bot/cell_workers/spin_fort.py similarity index 85% rename from pokemongo_bot/cell_workers/seen_fort_worker.py rename to pokemongo_bot/cell_workers/spin_fort.py index 16990cd5ce..2aae7cd490 100644 --- a/pokemongo_bot/cell_workers/seen_fort_worker.py +++ b/pokemongo_bot/cell_workers/spin_fort.py @@ -8,20 +8,15 @@ from pokemongo_bot.constants import Constants from pokemongo_bot.human_behaviour import sleep from pokemongo_bot.worker_result import WorkerResult -from utils import distance, format_time +from utils import distance, format_time, fort_details -class SeenFortWorker(object): +class SpinFort(object): def __init__(self, bot): - self.api = bot.api self.bot = bot - self.fort_timeouts = bot.fort_timeouts - self.position = bot.position - self.config = bot.config - self.item_list = bot.item_list def should_run(self): - return self.config.forts_spin and self.bot.has_space_for_loot() + return self.bot.config.forts_spin and self.bot.has_space_for_loot() def work(self): fort = self.get_fort_in_range() @@ -32,14 +27,17 @@ def work(self): lat = fort['latitude'] lng = fort['longitude'] + details = fort_details(self.bot, fort['id'], lat, lng) + fort_name = details.get('name', 'Unknown').encode('utf8', 'replace') + logger.log('Now at Pokestop: {0}'.format(fort_name), 'cyan') logger.log('Spinning ...', 'cyan') - self.api.fort_search(fort_id=fort['id'], + self.bot.api.fort_search(fort_id=fort['id'], fort_latitude=lat, fort_longitude=lng, - player_latitude=f2i(self.position[0]), - player_longitude=f2i(self.position[1])) - response_dict = self.api.call() + player_latitude=f2i(self.bot.position[0]), + player_longitude=f2i(self.bot.position[1])) + response_dict = self.bot.api.call() if 'responses' in response_dict and \ 'FORT_SEARCH' in response_dict['responses']: @@ -66,7 +64,7 @@ def work(self): tmp_count_items[item_id] += item['item_count'] for item_id, item_count in tmp_count_items.iteritems(): - item_name = self.item_list[str(item_id)] + item_name = self.bot.item_list[str(item_id)] logger.log( '- ' + str(item_count) + "x " + item_name + " (Total: " + str(self.bot.item_inventory_count(item_id)) + ")", 'yellow' @@ -119,7 +117,7 @@ def work(self): def get_fort_in_range(self): forts = self.bot.get_forts(order_by_distance=True) - forts = filter(lambda x: x["id"] not in self.fort_timeouts, forts) + forts = filter(lambda x: x["id"] not in self.bot.fort_timeouts, forts) if len(forts) == 0: return None @@ -127,8 +125,8 @@ def get_fort_in_range(self): fort = forts[0] distance_to_fort = distance( - self.position[0], - self.position[1], + self.bot.position[0], + self.bot.position[1], fort['latitude'], fort['longitude'] ) diff --git a/pokemongo_bot/cell_workers/pokemon_transfer_worker.py b/pokemongo_bot/cell_workers/transfer_pokemon.py similarity index 90% rename from pokemongo_bot/cell_workers/pokemon_transfer_worker.py rename to pokemongo_bot/cell_workers/transfer_pokemon.py index e546a46ca5..2eedc7eba3 100644 --- a/pokemongo_bot/cell_workers/pokemon_transfer_worker.py +++ b/pokemongo_bot/cell_workers/transfer_pokemon.py @@ -4,16 +4,13 @@ from pokemongo_bot.human_behaviour import action_delay -class PokemonTransferWorker(object): +class TransferPokemon(object): def __init__(self, bot): - self.config = bot.config - self.pokemon_list = bot.pokemon_list - self.api = bot.api self.bot = bot def work(self): - if not self.config.release_pokemon: + if not self.bot.config.release_pokemon: return pokemon_groups = self._release_pokemon_get_groups() @@ -21,7 +18,7 @@ def work(self): group = pokemon_groups[pokemon_id] if len(group) > 0: - pokemon_name = self.pokemon_list[pokemon_id - 1]['Name'] + pokemon_name = self.bot.pokemon_list[pokemon_id - 1]['Name'] keep_best, keep_best_cp, keep_best_iv = self._validate_keep_best_config(pokemon_name) if keep_best: @@ -75,15 +72,15 @@ def work(self): def _release_pokemon_get_groups(self): pokemon_groups = {} - self.api.get_player().get_inventory() - inventory_req = self.api.call() + self.bot.api.get_player().get_inventory() + inventory_req = self.bot.api.call() if inventory_req.get('responses', False) is False: return pokemon_groups inventory_dict = inventory_req['responses']['GET_INVENTORY']['inventory_delta']['inventory_items'] - user_web_inventory = 'web/inventory-%s.json' % (self.config.username) + user_web_inventory = 'web/inventory-%s.json' % (self.bot.config.username) with open(user_web_inventory, 'w') as outfile: json.dump(inventory_dict, outfile) @@ -96,6 +93,11 @@ def _release_pokemon_get_groups(self): continue pokemon_data = pokemon['inventory_item_data']['pokemon_data'] + + # pokemon in fort, so we cant transfer it + if 'deployed_fort_id' in pokemon_data and pokemon_data['deployed_fort_id']: + continue + group_id = pokemon_data['pokemon_id'] group_pokemon_cp = pokemon_data['cp'] group_pokemon_iv = self.get_pokemon_potential(pokemon_data) @@ -169,14 +171,14 @@ def release_pokemon(self, pokemon_name, cp, iv, pokemon_id): logger.log('Exchanging {} [CP {}] [Potential {}] for candy!'.format(pokemon_name, cp, iv), 'green') - self.api.release_pokemon(pokemon_id=pokemon_id) - response_dict = self.api.call() - action_delay(self.config.action_wait_min, self.config.action_wait_max) + self.bot.api.release_pokemon(pokemon_id=pokemon_id) + response_dict = self.bot.api.call() + action_delay(self.bot.config.action_wait_min, self.bot.config.action_wait_max) def _get_release_config_for(self, pokemon): - release_config = self.config.release.get(pokemon) + release_config = self.bot.config.release.get(pokemon) if not release_config: - release_config = self.config.release.get('any') + release_config = self.bot.config.release.get('any') if not release_config: release_config = {} return release_config diff --git a/pokemongo_bot/cell_workers/utils.py b/pokemongo_bot/cell_workers/utils.py index 300b5e93dd..507ae19583 100644 --- a/pokemongo_bot/cell_workers/utils.py +++ b/pokemongo_bot/cell_workers/utils.py @@ -14,6 +14,26 @@ (86400*7, 'week') ) +FORT_CACHE = {} +def fort_details(bot, fort_id, latitude, longitude): + """ + Lookup fort metadata and (if possible) serve from cache. + """ + + if fort_id not in FORT_CACHE: + """ + Lookup the fort details and cache the response for future use. + """ + bot.api.fort_details(fort_id=fort_id, latitude=latitude, longitude=longitude) + + try: + response_dict = bot.api.call() + FORT_CACHE[fort_id] = response_dict['responses']['FORT_DETAILS'] + except Exception: + pass + + # Just to avoid KeyErrors + return FORT_CACHE.get(fort_id, {}) def encode(cellid): output = [] diff --git a/pokemongo_bot/logger.py b/pokemongo_bot/logger.py index 054352c452..36c99c92f5 100644 --- a/pokemongo_bot/logger.py +++ b/pokemongo_bot/logger.py @@ -1,3 +1,4 @@ +from __future__ import unicode_literals import time try: @@ -18,9 +19,18 @@ def log(string, color='white'): 'cyan': '96m' } if color not in color_hex: - print('[' + time.strftime("%H:%M:%S") + '] ' + string) + print('[{time}] {string}'.format( + time=time.strftime("%H:%M:%S"), + string=string.decode('utf-8') + )) else: - print('[' + time.strftime("%H:%M:%S") + '] ' + u'\033[' + color_hex[color] + string.decode('utf-8') + '\033[0m') + print( + '[{time}] \033[{color} {string} \033[0m'.format( + time=time.strftime("%H:%M:%S"), + color=color_hex[color], + string=string.decode('utf-8') + ) + ) if lcd: if string: lcd.message(string) diff --git a/pokemongo_bot/navigators/__init__.py b/pokemongo_bot/navigators/__init__.py new file mode 100644 index 0000000000..3611385377 --- /dev/null +++ b/pokemongo_bot/navigators/__init__.py @@ -0,0 +1,2 @@ +from spiral_navigator import SpiralNavigator +from path_navigator import PathNavigator diff --git a/pokemongo_bot/navigators/path_navigator.py b/pokemongo_bot/navigators/path_navigator.py new file mode 100644 index 0000000000..634d52a51e --- /dev/null +++ b/pokemongo_bot/navigators/path_navigator.py @@ -0,0 +1,96 @@ +# -*- coding: utf-8 -*- + +import gpxpy +import gpxpy.gpx +import json +import pokemongo_bot.logger as logger +from pokemongo_bot.cell_workers.utils import distance, i2f, format_dist +from pokemongo_bot.human_behaviour import sleep +from pokemongo_bot.step_walker import StepWalker +from pgoapi.utilities import f2i + + +class PathNavigator(object): + + def __init__(self, bot): + self.bot = bot + self.api = bot.api + self.config = bot.config + self.ptr = 0 + self.points = self.load_path() + + def load_path(self): + if self.config.navigator_path_file == None: + raise RuntimeError('You need to specify a path file (json or gpx)') + + if self.config.navigator_path_file.endswith('.json'): + return self.load_json(self.config.navigator_path_file) + elif self.config.navigator_path_file.endswith('.gpx'): + return self.load_gpx(self.config.navigator_path_file) + + def load_json(self, file): + with open(file) as data_file: + points=json.load(data_file) + # Replace Verbal Location with lat&lng. + logger.log("Resolving Navigation Paths (GeoLocating Strings)") + for index, point in enumerate(points): + if self.bot.config.debug: + logger.log("Resolving Point {} - {}".format(index, point)) + point_tuple = self.bot.get_pos_by_name(point['location']) + points[index] = self.lat_lng_tuple_to_dict(point_tuple) + return points + + def lat_lng_tuple_to_dict(self, tpl): + return {'lat': tpl[0], 'lng': tpl[1]} + + def load_gpx(self, file): + gpx_file = open(file, 'r') + gpx = gpxpy.parse(gpx_file) + + if len(gpx.tracks) == 0: + raise RuntimeError('GPX file does not cotain a track') + + points = [] + track = gpx.tracks[0] + for segment in track.segments: + for point in segment.points: + points.append({"lat": point.latitude, "lng": point.longitude}) + + return points; + + def take_step(self): + point = self.points[self.ptr] + lat = float(point['lat']) + lng = float(point['lng']) + + if self.config.walk > 0: + step_walker = StepWalker( + self.bot, + self.config.walk, + lat, + lng + ) + + is_at_destination = False + if step_walker.step(): + is_at_destination = True + + else: + self.api.set_position(lat, lng) + + dist = distance( + self.api._position_lat, + self.api._position_lng, + lat, + lng + ) + + if dist <= 1 or (self.config.walk > 0 and is_at_destination): + if (self.ptr + 1) == len(self.points): + self.ptr = 0 + if self.config.navigator_path_mode == 'linear': + self.points = list(reversed(self.points)) + else: + self.ptr += 1 + + return [lat, lng] diff --git a/pokemongo_bot/spiral_navigator.py b/pokemongo_bot/navigators/spiral_navigator.py similarity index 79% rename from pokemongo_bot/spiral_navigator.py rename to pokemongo_bot/navigators/spiral_navigator.py index a4cfae983f..e91e93fbd0 100644 --- a/pokemongo_bot/spiral_navigator.py +++ b/pokemongo_bot/navigators/spiral_navigator.py @@ -1,7 +1,8 @@ # -*- coding: utf-8 -*- -import logger -from cell_workers.utils import distance, format_dist -from step_walker import StepWalker +from __future__ import absolute_import, unicode_literals +import pokemongo_bot.logger as logger +from pokemongo_bot.cell_workers.utils import distance, format_dist +from pokemongo_bot.step_walker import StepWalker class SpiralNavigator(object): @@ -14,14 +15,25 @@ def __init__(self, bot): self.origin_lat = self.bot.position[0] self.origin_lon = self.bot.position[1] - self.points = self._generate_spiral(self.origin_lat, self.origin_lon, 0.0018, self.steplimit) + self.points = self._generate_spiral( + self.origin_lat, self.origin_lon, 0.0018, self.steplimit + ) self.ptr = 0 self.direction = 1 self.cnt = 0 - # Source: https://github.com/tejado/pgoapi/blob/master/examples/spiral_poi_search.py @staticmethod def _generate_spiral(starting_lat, starting_lng, step_size, step_limit): + """ + Sourced from: + https://github.com/tejado/pgoapi/blob/master/examples/spiral_poi_search.py + + :param starting_lat: + :param starting_lng: + :param step_size: + :param step_limit: + :return: + """ coords = [{'lat': starting_lat, 'lng': starting_lng}] steps, x, y, d, m = 1, 0, 0, 1, 1 @@ -63,7 +75,8 @@ def take_step(self): ) if self.cnt == 1: - logger.log('Walking from ' + str((self.api._position_lat, + logger.log( + 'Walking from ' + str((self.api._position_lat, self.api._position_lng)) + " to " + str([point['lat'], point['lng']]) + " " + format_dist(dist, self.config.distance_unit)) diff --git a/pokemongo_bot/test/tree_config_builder_test.py b/pokemongo_bot/test/tree_config_builder_test.py new file mode 100644 index 0000000000..0c2402c429 --- /dev/null +++ b/pokemongo_bot/test/tree_config_builder_test.py @@ -0,0 +1,64 @@ +import unittest +import json +from pokemongo_bot import PokemonGoBot, ConfigException, TreeConfigBuilder +from pokemongo_bot.cell_workers import HandleSoftBan, CatchLuredPokemon + +def convert_from_json(str): + return json.loads(str) + +class TreeConfigBuilderTest(unittest.TestCase): + def setUp(self): + self.bot = {} + + def test_should_throw_on_no_type_key(self): + obj = convert_from_json("""[{ + "bad_key": "foo" + }]""") + + builder = TreeConfigBuilder(self.bot, obj) + + self.assertRaisesRegexp( + ConfigException, + "No type found for given task", + builder.build) + + def test_should_throw_on_non_matching_type(self): + obj = convert_from_json("""[{ + "type": "foo" + }]""") + + builder = TreeConfigBuilder(self.bot, obj) + + self.assertRaisesRegexp( + ConfigException, + "No worker named foo defined", + builder.build) + + def test_creating_worker(self): + obj = convert_from_json("""[{ + "type": "HandleSoftBan" + }]""") + + builder = TreeConfigBuilder(self.bot, obj) + tree = builder.build() + + self.assertIsInstance(tree[0], HandleSoftBan) + self.assertIs(tree[0].bot, self.bot) + + def test_creating_two_workers(self): + obj = convert_from_json("""[{ + "type": "HandleSoftBan" + }, { + "type": "CatchLuredPokemon" + }]""") + + builder = TreeConfigBuilder(self.bot, obj) + tree = builder.build() + + self.assertIsInstance(tree[0], HandleSoftBan) + self.assertIs(tree[0].bot, self.bot) + self.assertIsInstance(tree[1], CatchLuredPokemon) + self.assertIs(tree[1].bot, self.bot) + +if __name__ == '__main__': + unittest.main() diff --git a/pokemongo_bot/tree_config_builder.py b/pokemongo_bot/tree_config_builder.py new file mode 100644 index 0000000000..35c16f309f --- /dev/null +++ b/pokemongo_bot/tree_config_builder.py @@ -0,0 +1,35 @@ +import cell_workers + +class ConfigException(Exception): + pass + +class TreeConfigBuilder(object): + def __init__(self, bot, tasks_raw): + self.bot = bot + self.tasks_raw = tasks_raw + + def build(self): + print 'build' + + def _get_worker_by_name(self, name): + try: + worker = getattr(cell_workers, name) + except AttributeError: + raise ConfigException('No worker named {} defined'.format(name)) + + return worker + + def build(self): + workers = [] + + for task in self.tasks_raw: + task_type = task.get('type', None) + if task_type is None: + raise ConfigException('No type found for given task {}'.format(task)) + + worker = self._get_worker_by_name(task_type) + instance = worker(self.bot) + workers.append(instance) + + return workers + diff --git a/requirements.txt b/requirements.txt index 354382d815..6963189fa6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -16,3 +16,4 @@ flask==0.11.1 socketIO_client==0.7.0 eventlet==0.19.0 universal-analytics-python==0.2.4 +gpxpy==1.1.1 From eb49f46c9e0fb912bd7b0e4a488d9529ec5c96e0 Mon Sep 17 00:00:00 2001 From: Astral Date: Sat, 30 Jul 2016 16:42:18 -0700 Subject: [PATCH 4/4] Adding recycle_items back Changed error return type --- pokecli.py | 3 ++- pokemongo_bot/cell_workers/recycle_items.py | 25 ++++++--------------- 2 files changed, 9 insertions(+), 19 deletions(-) diff --git a/pokecli.py b/pokecli.py index fb903bfea0..26056c31a3 100755 --- a/pokecli.py +++ b/pokecli.py @@ -445,7 +445,8 @@ def init_config(): item_list = json.load(open(os.path.join('data', 'items.json'))) for config_item_name, bag_count in config.item_filter.iteritems(): if config_item_name not in item_list.viewvalues(): - raise Exception('Config.json error - item "' + config_item_name + '" does not exist (spelling mistake?)') + parser.error('item "' + config_item_name + '" does not exist, spelling mistake? (check for valid item names in data/items.json)') + return None # create web dir if not exists try: diff --git a/pokemongo_bot/cell_workers/recycle_items.py b/pokemongo_bot/cell_workers/recycle_items.py index 611b7e3d30..aba0f46a67 100644 --- a/pokemongo_bot/cell_workers/recycle_items.py +++ b/pokemongo_bot/cell_workers/recycle_items.py @@ -12,33 +12,22 @@ def work(self): for item_id, bag_count in item_count_dict.iteritems(): item_name = self.bot.item_list[str(item_id)] - id_filter = self.bot.config.item_filter.get(str(item_id), 0) + id_filter = self.bot.config.item_filter.get(item_name, 0) if id_filter is not 0: id_filter_keep = id_filter.get('keep', 20) bag_count = self.bot.item_inventory_count(item_id) - if str(item_id) in self.bot.config.item_filter and bag_count > id_filter_keep: + if item_name in self.bot.config.item_filter and bag_count > id_filter_keep: items_recycle_count = bag_count - id_filter_keep - - response_dict_recycle = self.send_recycle_item_request( - item_id=item_id, - count=items_recycle_count - ) - - result = response_dict_recycle.get('responses', {}) \ - .get('RECYCLE_INVENTORY_ITEM', {}) \ - .get('result', 0) + response_dict_recycle = self.send_recycle_item_request(item_id=item_id, count=items_recycle_count) + result = response_dict_recycle.get('responses', {}).get('RECYCLE_INVENTORY_ITEM', {}).get('result', 0) if result == 1: # Request success - message_template = "-- Recycled {}x {} (keeps only {} maximum) " - message = message_template.format( - str(items_recycle_count), - item_name, - str(id_filter_keep) - ) + message_template = "-- Discarded {}x {} (keeps only {} maximum) " + message = message_template.format(str(items_recycle_count), item_name, str(id_filter_keep)) logger.log(message, 'green') else: - logger.log("-- Failed to recycle " + item_name + "has failed!", 'red') + logger.log("-- Failed to discard " + item_name, 'red') def send_recycle_item_request(self, item_id, count): self.bot.api.recycle_inventory_item(item_id=item_id, count=count)