From 794a959ba7aa88fa73425d81e0061af0d9997975 Mon Sep 17 00:00:00 2001 From: Gobberwart Date: Sun, 28 Aug 2016 04:06:11 +1000 Subject: [PATCH 01/23] Fix bot crash at start on permaban --- pokecli.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pokecli.py b/pokecli.py index d57aa76caa..f9e1cec5ce 100644 --- a/pokecli.py +++ b/pokecli.py @@ -80,6 +80,9 @@ def initialize_task(bot, config): def initialize(config): bot = PokemonGoBot(config) + return bot + + def start_bot(bot,config): bot.start() initialize_task(bot,config) bot.metrics.capture_stats() @@ -113,6 +116,7 @@ def get_commit_hash(): while not finished: try: bot = initialize(config) + bot = start_bot(bot,config) config_changed = check_mod(config_file) bot.event_manager.emit( From 041e5ff0b52032316a718e5c496f239ae35fbaba Mon Sep 17 00:00:00 2001 From: Gobberwart Date: Sun, 28 Aug 2016 09:09:43 +1000 Subject: [PATCH 02/23] Expanded logging options Added "logging" section to config, with options "color", "show_datetime", "show_process_name" and "show_log_level" --- configs/config.json.cluster.example | 10 ++++++---- configs/config.json.map.example | 10 ++++++---- configs/config.json.optimizer.example | 10 ++++++---- configs/config.json.path.example | 10 ++++++---- configs/config.json.pokemon.example | 10 ++++++---- pokecli.py | 3 ++- pokemongo_bot/__init__.py | 14 +++++++++++--- 7 files changed, 43 insertions(+), 24 deletions(-) diff --git a/configs/config.json.cluster.example b/configs/config.json.cluster.example index 5e63052f69..ed7d9e6508 100644 --- a/configs/config.json.cluster.example +++ b/configs/config.json.cluster.example @@ -228,10 +228,12 @@ "location_cache": true, "distance_unit": "km", "reconnecting_timeout": 15, - "logging": { - "color": true, - "clean": false - }, + "logging": { + "color": true, + "show_datetime": true, + "show_process_name": true, + "show_log_level": true + }, "catch": { "any": {"catch_above_cp": 0, "catch_above_iv": 0, "logic": "or"}, "// Example of always catching Rattata:": {}, diff --git a/configs/config.json.map.example b/configs/config.json.map.example index d675f18998..ea85fe44ed 100644 --- a/configs/config.json.map.example +++ b/configs/config.json.map.example @@ -485,10 +485,12 @@ "location_cache": true, "distance_unit": "km", "reconnecting_timeout": 15, - "logging": { - "color": true, - "clean": false - }, + "logging": { + "color": true, + "show_datetime": true, + "show_process_name": true, + "show_log_level": true + }, "catch": { "any": {"catch_above_cp": 0, "catch_above_iv": 0, "logic": "or"}, "// Example of always catching Rattata:": {}, diff --git a/configs/config.json.optimizer.example b/configs/config.json.optimizer.example index 70acc217f8..0f1da751d3 100644 --- a/configs/config.json.optimizer.example +++ b/configs/config.json.optimizer.example @@ -298,10 +298,12 @@ "location_cache": true, "distance_unit": "km", "reconnecting_timeout": 15, - "logging": { - "color": true, - "clean": false - }, + "logging": { + "color": true, + "show_datetime": true, + "show_process_name": true, + "show_log_level": true + }, "catch": { "any": { "always_catch": true diff --git a/configs/config.json.path.example b/configs/config.json.path.example index 9610fed2b5..2e053d6d8d 100644 --- a/configs/config.json.path.example +++ b/configs/config.json.path.example @@ -234,10 +234,12 @@ "location_cache": true, "distance_unit": "km", "reconnecting_timeout": 15, - "logging": { - "color": true, - "clean": false - }, + "logging": { + "color": true, + "show_datetime": true, + "show_process_name": true, + "show_log_level": true + }, "catch": { "any": {"catch_above_cp": 0, "catch_above_iv": 0, "logic": "or"}, "// Example of always catching Rattata:": {}, diff --git a/configs/config.json.pokemon.example b/configs/config.json.pokemon.example index 5592ab14f8..37d3cf98e7 100644 --- a/configs/config.json.pokemon.example +++ b/configs/config.json.pokemon.example @@ -238,10 +238,12 @@ "location_cache": true, "distance_unit": "km", "reconnecting_timeout": 15, - "logging": { - "color": true, - "clean": false - }, + "logging": { + "color": true, + "show_datetime": true, + "show_process_name": true, + "show_log_level": true + }, "catch": { "any": {"catch_above_cp": 0, "catch_above_iv": 0, "logic": "or" }, diff --git a/pokecli.py b/pokecli.py index a5a71629a7..ec07497564 100644 --- a/pokecli.py +++ b/pokecli.py @@ -630,7 +630,7 @@ def _json_loader(filename): type=bool, default=False ) - + # Start to parse other attrs config = parser.parse_args() if not config.username and 'username' not in load: @@ -650,6 +650,7 @@ def _json_loader(filename): config.live_config_update = load.get('live_config_update', {}) config.live_config_update_enabled = config.live_config_update.get('enabled', False) config.live_config_update_tasks_only = config.live_config_update.get('tasks_only', False) + config.logging = load.get('logging', {}) if config.map_object_cache_time < 0.0: parser.error("--map_object_cache_time is out of range! (should be >= 0.0)") diff --git a/pokemongo_bot/__init__.py b/pokemongo_bot/__init__.py index 61c9c2d758..c908e95820 100644 --- a/pokemongo_bot/__init__.py +++ b/pokemongo_bot/__init__.py @@ -132,7 +132,7 @@ def start(self): def _setup_event_system(self): handlers = [] - if self.config.logging_color: + if self.config.logging and 'color' in self.config.logging and self.config.logging['color']: handlers.append(ColoredLoggingHandler(self)) else: handlers.append(LoggingHandler(self)) @@ -760,8 +760,16 @@ def _setup_logging(self): logging.getLogger("pgoapi").setLevel(log_level) logging.getLogger("rpc_api").setLevel(log_level) - if self.config.logging_clean and not self.config.debug: - formatter = Formatter(fmt='[%(asctime)s] %(message)s', datefmt='%H:%M:%S') + if self.config.logging: + logging_format = '%(message)s' + if ('show_log_level' in self.config.logging and self.config.logging['show_log_level']) or 'show_log_level' not in self.config.logging: + logging_format = '[%(levelname)s] ' + logging_format + if ('show_process_name' in self.config.logging and self.config.logging['show_process_name']) or 'show_process_name' not in self.config.logging: + logging_format = '[%(name)10s] ' + logging_format + if ('show_datetime' in self.config.logging and self.config.logging['show_datetime']) or 'show_datetime' not in self.config.logging: + logging_format = '[%(asctime)s] ' + logging_format + + formatter = Formatter(fmt=logging_format) for handler in logging.root.handlers[:]: handler.setFormatter(formatter) From bb844b9ea7fed6cc23ccc53a6ed0fe2b376c0234 Mon Sep 17 00:00:00 2001 From: Gobberwart Date: Sun, 28 Aug 2016 09:23:49 +1000 Subject: [PATCH 03/23] Added warning about deprecated logging_color arg --- pokemongo_bot/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pokemongo_bot/__init__.py b/pokemongo_bot/__init__.py index c908e95820..f3f8edfbd2 100644 --- a/pokemongo_bot/__init__.py +++ b/pokemongo_bot/__init__.py @@ -132,6 +132,9 @@ def start(self): def _setup_event_system(self): handlers = [] + if self.config.logging_color: + self.logger.warning('The logging_color argument has been moved into the logging config section') + if self.config.logging and 'color' in self.config.logging and self.config.logging['color']: handlers.append(ColoredLoggingHandler(self)) else: From 0cf965547ef22fb730e31833fdce80390042758e Mon Sep 17 00:00:00 2001 From: Gobberwart Date: Sun, 28 Aug 2016 09:39:37 +1000 Subject: [PATCH 04/23] Display log message moved No point trying to use the logger before it's been initialised. Moved to init_config. --- pokecli.py | 5 ++++- pokemongo_bot/__init__.py | 3 --- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pokecli.py b/pokecli.py index ec07497564..141ba87f20 100644 --- a/pokecli.py +++ b/pokecli.py @@ -695,7 +695,10 @@ def task_configuration_error(flag_name): if "daily_catch_limit" in load: logger.warning('The daily_catch_limit argument has been moved into the CatchPokemon Task') - + + if "logging_color" in load: + logger.warning('The logging_color argument has been moved into the logging config section') + if config.walk_min < 1: parser.error("--walk_min is out of range! (should be >= 1.0)") return None diff --git a/pokemongo_bot/__init__.py b/pokemongo_bot/__init__.py index f3f8edfbd2..c908e95820 100644 --- a/pokemongo_bot/__init__.py +++ b/pokemongo_bot/__init__.py @@ -132,9 +132,6 @@ def start(self): def _setup_event_system(self): handlers = [] - if self.config.logging_color: - self.logger.warning('The logging_color argument has been moved into the logging config section') - if self.config.logging and 'color' in self.config.logging and self.config.logging['color']: handlers.append(ColoredLoggingHandler(self)) else: From f88997d702cfa2206e14c3f4822e1e39b60827ad Mon Sep 17 00:00:00 2001 From: Gobberwart Date: Sun, 28 Aug 2016 09:58:29 +1000 Subject: [PATCH 05/23] Remove milliseconds from datetime Because really, do we need that? --- pokemongo_bot/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pokemongo_bot/__init__.py b/pokemongo_bot/__init__.py index c908e95820..d9e616025e 100644 --- a/pokemongo_bot/__init__.py +++ b/pokemongo_bot/__init__.py @@ -762,14 +762,16 @@ def _setup_logging(self): if self.config.logging: logging_format = '%(message)s' + logging_format_options = '' if ('show_log_level' in self.config.logging and self.config.logging['show_log_level']) or 'show_log_level' not in self.config.logging: logging_format = '[%(levelname)s] ' + logging_format if ('show_process_name' in self.config.logging and self.config.logging['show_process_name']) or 'show_process_name' not in self.config.logging: logging_format = '[%(name)10s] ' + logging_format if ('show_datetime' in self.config.logging and self.config.logging['show_datetime']) or 'show_datetime' not in self.config.logging: logging_format = '[%(asctime)s] ' + logging_format + logging_format_options = '%Y-%m-%d %H:%M:%S' - formatter = Formatter(fmt=logging_format) + formatter = Formatter(logging_format,logging_format_options) for handler in logging.root.handlers[:]: handler.setFormatter(formatter) From c9164a76928b7faf50fe707c0bf4792f1bffe98e Mon Sep 17 00:00:00 2001 From: Gobberwart Date: Sun, 28 Aug 2016 15:39:46 +1000 Subject: [PATCH 06/23] Reversed condition order for clarity First check: "if not in config", OR Second check: "is in config AND set to true" If either condition matches, the logging detail will be displayed. --- pokemongo_bot/__init__.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/pokemongo_bot/__init__.py b/pokemongo_bot/__init__.py index d9e616025e..14058976a9 100644 --- a/pokemongo_bot/__init__.py +++ b/pokemongo_bot/__init__.py @@ -763,11 +763,15 @@ def _setup_logging(self): if self.config.logging: logging_format = '%(message)s' logging_format_options = '' - if ('show_log_level' in self.config.logging and self.config.logging['show_log_level']) or 'show_log_level' not in self.config.logging: + + if 'show_log_level' not in self.config.logging or \ + ('show_log_level' in self.config.logging and self.config.logging['show_log_level']): logging_format = '[%(levelname)s] ' + logging_format - if ('show_process_name' in self.config.logging and self.config.logging['show_process_name']) or 'show_process_name' not in self.config.logging: + if 'show_process_name' not in self.config.logging or \ + ('show_process_name' in self.config.logging and self.config.logging['show_process_name']): logging_format = '[%(name)10s] ' + logging_format - if ('show_datetime' in self.config.logging and self.config.logging['show_datetime']) or 'show_datetime' not in self.config.logging: + if 'show_datetime' not in self.config.logging or \ + ('show_datetime' in self.config.logging and self.config.logging['show_datetime']): logging_format = '[%(asctime)s] ' + logging_format logging_format_options = '%Y-%m-%d %H:%M:%S' From 1ccde940d7012aa59e67aae9f520fabfad033385 Mon Sep 17 00:00:00 2001 From: Gobberwart Date: Sun, 28 Aug 2016 15:53:33 +1000 Subject: [PATCH 07/23] Documented new log options --- docs/configuration_files.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/configuration_files.md b/docs/configuration_files.md index ef47e7d07d..072b4b429d 100644 --- a/docs/configuration_files.md +++ b/docs/configuration_files.md @@ -87,6 +87,14 @@ Document the configuration options of PokemonGo-Bot. | `live_config_update.tasks_only` | false | True: quick update for Tasks only (without re-login). False: slower update for entire config file. +## Logging configuration +[[back to top](#table-of-contents)] + +'logging'.'color' (default false) Enabled colored logging +'logging'.'show_datetime' (default true) Show date and time in log +'logging'.'show_process_name' (default true) Show name of process generating output in log +'logging'.'show_log_level' (default true) Show level of log message in log (eg. "INFO") + ## Configuring Tasks [[back to top](#table-of-contents)] From 33e10953c4b1ca7602442c6f3807cf05a12aab28 Mon Sep 17 00:00:00 2001 From: Gobberwart Date: Sun, 28 Aug 2016 16:05:29 +1000 Subject: [PATCH 08/23] Modified conditions again Removed unnecessary second check for config values and slightly modified parentheses as per suggestion from @mjmadsen --- configs/config.json.example | 10 ++++++---- pokemongo_bot/__init__.py | 9 +++------ 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/configs/config.json.example b/configs/config.json.example index e11dedd587..634ab60b13 100644 --- a/configs/config.json.example +++ b/configs/config.json.example @@ -266,10 +266,12 @@ "location_cache": true, "distance_unit": "km", "reconnecting_timeout": 15, - "logging": { - "color": true, - "clean": false - }, + "logging": { + "color": true, + "show_datetime": true, + "show_process_name": true, + "show_log_level": true + }, "catch": { "any": {"candy_threshold" : 400 ,"catch_above_cp": 0, "catch_above_iv": 0, "logic": "or"}, "// Example of always catching Rattata:": {}, diff --git a/pokemongo_bot/__init__.py b/pokemongo_bot/__init__.py index 14058976a9..3251d161c0 100644 --- a/pokemongo_bot/__init__.py +++ b/pokemongo_bot/__init__.py @@ -764,14 +764,11 @@ def _setup_logging(self): logging_format = '%(message)s' logging_format_options = '' - if 'show_log_level' not in self.config.logging or \ - ('show_log_level' in self.config.logging and self.config.logging['show_log_level']): + 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 \ - ('show_process_name' in self.config.logging and self.config.logging['show_process_name']): + 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 \ - ('show_datetime' in self.config.logging and self.config.logging['show_datetime']): + 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' From fd40f7cbd7aeaa1f84946ccfa98726af43043674 Mon Sep 17 00:00:00 2001 From: Gobberwart Date: Mon, 29 Aug 2016 09:41:59 +1000 Subject: [PATCH 09/23] Add exception handling to json file read/write ops --- pokemongo_bot/__init__.py | 19 ++++++++++-- .../cell_workers/pokemon_optimizer.py | 25 +--------------- pokemongo_bot/cell_workers/telegram_task.py | 21 ++++++++++---- .../cell_workers/update_live_stats.py | 27 ++++++++++++++--- pokemongo_bot/inventory.py | 29 +++++++++++++++---- 5 files changed, 79 insertions(+), 42 deletions(-) diff --git a/pokemongo_bot/__init__.py b/pokemongo_bot/__init__.py index 5d9a945628..03d76dc7ce 100644 --- a/pokemongo_bot/__init__.py +++ b/pokemongo_bot/__init__.py @@ -41,6 +41,9 @@ from pgoapi.protos.POGOProtos.Enums import BadgeType_pb2 import struct +class FileIOException(Exception): + pass + class PokemonGoBot(Datastore): @property def position(self): @@ -1095,9 +1098,19 @@ def _set_starting_position(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) + + 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'], diff --git a/pokemongo_bot/cell_workers/pokemon_optimizer.py b/pokemongo_bot/cell_workers/pokemon_optimizer.py index 7b51d2703c..41b1c06f96 100644 --- a/pokemongo_bot/cell_workers/pokemon_optimizer.py +++ b/pokemongo_bot/cell_workers/pokemon_optimizer.py @@ -96,30 +96,7 @@ def open_inventory(self): self.family_by_family_id.setdefault(family_id, []).append(pokemon) def save_web_inventory(self): - web_inventory = os.path.join(_base_dir, "web", "inventory-%s.json" % self.bot.config.username) - - with open(web_inventory, "r") as infile: - ii = json.load(infile) - - ii = [x for x in ii if not x.get("inventory_item_data", {}).get("pokedex_entry", None)] - ii = [x for x in ii if not x.get("inventory_item_data", {}).get("candy", None)] - ii = [x for x in ii if not x.get("inventory_item_data", {}).get("item", None)] - ii = [x for x in ii if not x.get("inventory_item_data", {}).get("pokemon_data", None)] - - for pokedex in inventory.pokedex().all(): - ii.append({"inventory_item_data": {"pokedex_entry": pokedex}}) - - for family_id, candy in inventory.candies()._data.items(): - ii.append({"inventory_item_data": {"candy": {"family_id": family_id, "candy": candy.quantity}}}) - - for item_id, item in inventory.items()._data.items(): - ii.append({"inventory_item_data": {"item": {"item_id": item_id, "count": item.count}}}) - - for pokemon in inventory.pokemons().all(): - ii.append({"inventory_item_data": {"pokemon_data": pokemon._data}}) - - with open(web_inventory, "w") as outfile: - json.dump(ii, outfile) + inventory.update_web_inventory() def get_family_optimized(self, family_id, family): evolve_best = [] diff --git a/pokemongo_bot/cell_workers/telegram_task.py b/pokemongo_bot/cell_workers/telegram_task.py index 6591b7183d..5c2b60787e 100644 --- a/pokemongo_bot/cell_workers/telegram_task.py +++ b/pokemongo_bot/cell_workers/telegram_task.py @@ -10,6 +10,9 @@ from pprint import pprint import re +class FileIOException(Exception): + pass + class TelegramTask(BaseTask): SUPPORTED_TASK_API_VERSION = 1 update_id = None @@ -18,7 +21,6 @@ class TelegramTask(BaseTask): def initialize(self): if not self.enabled: return - self.logger = logging.getLogger(type(self).__name__) api_key = self.bot.config.telegram_token if api_key == None: self.emit_event( @@ -40,7 +42,7 @@ def work(self): for update in self.tbot.getUpdates(offset=self.update_id, timeout=10): self.update_id = update.update_id+1 if update.message: - self.logger.info("message from {} ({}): {}".format(update.message.from_user.username, update.message.from_user.id, update.message.text)) + self.bot.logger.info("message from {} ({}): {}".format(update.message.from_user.username, update.message.from_user.id, update.message.text)) if self.config.get('master',None) and self.config.get('master',None) not in [update.message.from_user.id, "@{}".format(update.message.from_user.username)]: self.emit_event( 'debug', @@ -89,9 +91,18 @@ def _get_player_stats(self): :rtype: dict """ web_inventory = os.path.join(_base_dir, "web", "inventory-%s.json" % self.bot.config.username) - with open(web_inventory, "r") as infile: - json_inventory = json.load(infile) - infile.close() + + try: + with open(web_inventory, "r") as infile: + json_inventory = json.load(infile) + except ValueError: + # Unable to read json from web inventory + # File may be corrupt. Create a new one. + self.bot.logger.info('[x] Error while opening inventory file for read: %s' % e) + json_inventory = [] + except: + raise FileIOException("Unexpected error reading from {}".web_inventory) + return next((x["inventory_item_data"]["player_stats"] for x in json_inventory if x.get("inventory_item_data", {}).get("player_stats", {})), diff --git a/pokemongo_bot/cell_workers/update_live_stats.py b/pokemongo_bot/cell_workers/update_live_stats.py index 9fb8ea9d46..61e6c3dc9b 100644 --- a/pokemongo_bot/cell_workers/update_live_stats.py +++ b/pokemongo_bot/cell_workers/update_live_stats.py @@ -1,6 +1,8 @@ import ctypes import json import os +import logging + from sys import stdout, platform as _platform from datetime import datetime, timedelta @@ -12,6 +14,9 @@ # XP file import json +class FileIOException(Exception): + pass + class UpdateLiveStats(BaseTask): """ Periodically displays stats about the bot in the terminal and/or in its title. @@ -433,12 +438,26 @@ def _get_player_stats(self): def update_web_stats(self,player_data): web_inventory = os.path.join(_base_dir, "web", "inventory-%s.json" % self.bot.config.username) - with open(web_inventory, "r") as infile: - json_stats = json.load(infile) + try: + with open(web_inventory, "r") as infile: + json_stats = json.load(infile) + except (IOError, ValueError): + # Unable to read json from web inventory + # File may be corrupt. Create a new one. + self.bot.logger.info('[x] Error while opening inventory file for read: %s' % e, 'red') + json_stats = [] + except: + raise FileIOException("Unexpected error loading information from json.") json_stats = [x for x in json_stats if not x.get("inventory_item_data", {}).get("player_stats", None)] json_stats.append({"inventory_item_data": {"player_stats": player_data}}) - with open(web_inventory, "w") as outfile: - json.dump(json_stats, outfile) + try: + with open(web_inventory, "w") as outfile: + json.dump(json_stats, outfile) + except (IOError, ValueError): + self.bot.logger.info('[x] Error while opening inventory file for write: %s' % e, 'red') + pass + except: + raise FileIOException("Unexpected error writing to {}".web_inventory) diff --git a/pokemongo_bot/inventory.py b/pokemongo_bot/inventory.py index 0cffa16095..0459bd4d34 100644 --- a/pokemongo_bot/inventory.py +++ b/pokemongo_bot/inventory.py @@ -14,6 +14,9 @@ https://www.reddit.com/r/pokemongodev/comments/4w7mdg/combat_damage_calculation_formula_exactly/ ''' +class FileIOException(Exception): + pass + # # Abstraction @@ -1122,6 +1125,8 @@ def init_inventory_outfile(self): web_inventory = os.path.join(_base_dir, "web", "inventory-%s.json" % self.bot.config.username) if not os.path.exists(web_inventory): + self.bot.logger.info('No inventory file %s found. Creating a new one' % web_inventory) + json_inventory = [] with open(web_inventory, "w") as outfile: @@ -1134,9 +1139,16 @@ def update_web_inventory(self): if not os.path.exists(web_inventory): self.init_inventory_outfile() - with open(web_inventory, "r") as infile: - json_inventory = json.load(infile) - infile.close() + try: + with open(web_inventory, "r") as infile: + json_inventory = json.load(infile) + except (IOError, ValueError): + # Unable to read json from web inventory + # File may be corrupt. Create a new one. + self.bot.logger.info('[x] Error while opening inventory file for read: %s' % e, 'red') + json_inventory = [] + except: + raise FileIOException("Unexpected error reading from {}".web_inventory) json_inventory = [x for x in json_inventory if not x.get("inventory_item_data", {}).get("pokedex_entry", None)] json_inventory = [x for x in json_inventory if not x.get("inventory_item_data", {}).get("candy", None)] @@ -1145,9 +1157,14 @@ def update_web_inventory(self): json_inventory = json_inventory + self.jsonify_inventory() - with open(web_inventory, "w") as outfile: - json.dump(json_inventory, outfile) - outfile.close() + try: + with open(web_inventory, "w") as outfile: + json.dump(json_inventory, outfile) + except (IOError, ValueError): + self.bot.logger.info('[x] Error while opening inventory file for write: %s' % e, 'red') + pass + except: + raise FileIOException("Unexpected error writing to {}".web_inventory) def jsonify_inventory(self): json_inventory = [] From f0ce3caf640c00a58d2e66670b8c7135ed1040ab Mon Sep 17 00:00:00 2001 From: Gobberwart Date: Mon, 29 Aug 2016 11:34:06 +1000 Subject: [PATCH 10/23] Removed API call in update live stats Instead of making a new api call, utilise stats already contained in metrics. --- pokemongo_bot/cell_workers/update_live_stats.py | 12 ++---------- pokemongo_bot/metrics.py | 4 ++++ 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/pokemongo_bot/cell_workers/update_live_stats.py b/pokemongo_bot/cell_workers/update_live_stats.py index 61e6c3dc9b..743e3b9f19 100644 --- a/pokemongo_bot/cell_workers/update_live_stats.py +++ b/pokemongo_bot/cell_workers/update_live_stats.py @@ -425,16 +425,8 @@ def _get_player_stats(self): :rtype: dict """ # TODO : find a better solution than calling the api - inventory_items = self.bot.api.get_inventory() \ - .get('responses', {}) \ - .get('GET_INVENTORY', {}) \ - .get('inventory_delta', {}) \ - .get('inventory_items', {}) - return next((x["inventory_item_data"]["player_stats"] - for x in inventory_items - if x.get("inventory_item_data", {}).get("player_stats", {})), - None) - + return self.bot.metrics.player_stats + def update_web_stats(self,player_data): web_inventory = os.path.join(_base_dir, "web", "inventory-%s.json" % self.bot.config.username) diff --git a/pokemongo_bot/metrics.py b/pokemongo_bot/metrics.py index 775258155d..693a357cfd 100644 --- a/pokemongo_bot/metrics.py +++ b/pokemongo_bot/metrics.py @@ -24,6 +24,9 @@ def __init__(self, bot): self.uniq_pokemons_caught = None self.uniq_pokemons_list = None + + self.player_stats = [] + self.inventory_data = [] def runtime(self): return timedelta(seconds=round(time.time() - self.start_time)) @@ -114,6 +117,7 @@ def capture_stats(self): if 'inventory_item_data' in item: if 'player_stats' in item['inventory_item_data']: playerdata = item['inventory_item_data']['player_stats'] + self.player_stats = playerdata self.xp['latest'] = playerdata.get('experience', 0) if self.xp['start'] < 0: self.xp['start'] = self.xp['latest'] From d4c3efb7008db41300a824f8f3b471f8dfe44952 Mon Sep 17 00:00:00 2001 From: Gobberwart Date: Mon, 29 Aug 2016 12:12:33 +1000 Subject: [PATCH 11/23] Update player data in web from metrics Uses existing metrics instead of waiting on liveupdate --- pokemongo_bot/cell_workers/__init__.py | 1 + .../cell_workers/update_live_stats.py | 1 - .../cell_workers/update_web_inventory.py | 5 ++- .../cell_workers/update_web_playerdata.py | 43 +++++++++++++++++++ 4 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 pokemongo_bot/cell_workers/update_web_playerdata.py diff --git a/pokemongo_bot/cell_workers/__init__.py b/pokemongo_bot/cell_workers/__init__.py index 486dc9e1c1..9c9e9642a1 100644 --- a/pokemongo_bot/cell_workers/__init__.py +++ b/pokemongo_bot/cell_workers/__init__.py @@ -21,6 +21,7 @@ from complete_tutorial import CompleteTutorial from random_pause import RandomPause from update_web_inventory import UpdateWebInventory +from update_web_playerdata import UpdateWebPlayerdata from random_alive_pause import RandomAlivePause from show_best_pokemon import ShowBestPokemon from telegram_task import TelegramTask diff --git a/pokemongo_bot/cell_workers/update_live_stats.py b/pokemongo_bot/cell_workers/update_live_stats.py index 743e3b9f19..cfc84c0a37 100644 --- a/pokemongo_bot/cell_workers/update_live_stats.py +++ b/pokemongo_bot/cell_workers/update_live_stats.py @@ -332,7 +332,6 @@ def _get_stats_line(self, player_stats): # Gather stats values. metrics = self.bot.metrics - metrics.capture_stats() runtime = metrics.runtime() login = self.bot.config.username player_data = self.bot.player_data diff --git a/pokemongo_bot/cell_workers/update_web_inventory.py b/pokemongo_bot/cell_workers/update_web_inventory.py index 02b82128f3..34c5186981 100644 --- a/pokemongo_bot/cell_workers/update_web_inventory.py +++ b/pokemongo_bot/cell_workers/update_web_inventory.py @@ -1,5 +1,8 @@ +import json +import os from pokemongo_bot.base_task import BaseTask from pokemongo_bot import inventory +from pokemongo_bot.base_dir import _base_dir class UpdateWebInventory(BaseTask): @@ -9,4 +12,4 @@ def initialize(self): pass def work(self): - inventory.update_web_inventory() + inventory.update_web_inventory() \ No newline at end of file diff --git a/pokemongo_bot/cell_workers/update_web_playerdata.py b/pokemongo_bot/cell_workers/update_web_playerdata.py new file mode 100644 index 0000000000..5a50fed59a --- /dev/null +++ b/pokemongo_bot/cell_workers/update_web_playerdata.py @@ -0,0 +1,43 @@ +import json +import os +from pokemongo_bot.base_task import BaseTask +from pokemongo_bot.base_dir import _base_dir + + +class UpdateWebPlayerdata(BaseTask): + SUPPORTED_TASK_API_VERSION = 1 + + def initialize(self): + pass + + def work(self): + self.bot.metrics.capture_stats() + self.update_player_stats(self.bot.metrics.player_stats) + + def update_player_stats(self,player_data): + web_inventory = os.path.join(_base_dir, "web", "inventory-%s.json" % self.bot.config.username) + + try: + with open(web_inventory, "r") as infile: + json_stats = json.load(infile) + except (IOError, ValueError): + # Unable to read json from web inventory + # File may be corrupt. Create a new one. + self.bot.logger.info('[x] Error while opening inventory file for read: %s' % e, 'red') + json_stats = [] + except: + raise FileIOException("Unexpected error loading information from json.") + + json_stats = [x for x in json_stats if not x.get("inventory_item_data", {}).get("player_stats", None)] + + json_stats.append({"inventory_item_data": {"player_stats": player_data}}) + + try: + with open(web_inventory, "w") as outfile: + json.dump(json_stats, outfile) + except (IOError, ValueError): + self.bot.logger.info('[x] Error while opening inventory file for write: %s' % e, 'red') + pass + except: + raise FileIOException("Unexpected error writing to {}".web_inventory) + From 7fea872366e1a137eacd0a4ac838c0a84964c91e Mon Sep 17 00:00:00 2001 From: DBa2016 Date: Mon, 29 Aug 2016 00:34:29 +0200 Subject: [PATCH 12/23] Implemented more granularity in the "alert_catch" parameter for Telegram alerts. --- configs/config.json.example | 7 ++++++- .../event_handlers/telegram_handler.py | 18 +++++++++++++++--- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/configs/config.json.example b/configs/config.json.example index 634ab60b13..5cb0d9c2d9 100644 --- a/configs/config.json.example +++ b/configs/config.json.example @@ -12,7 +12,12 @@ "config": { "enabled": false, "master": null, - "alert_catch": ["all"] + "// old syntax, still supported: alert_catch": ["all"], + "// new syntax:": {}, + "alert_catch": { + "all": {"operator": "and", "cp": 1300, "iv": 0.95}, + "Snorlax": {"operator": "or", "cp": 900, "iv": 0.9} + } } }, { diff --git a/pokemongo_bot/event_handlers/telegram_handler.py b/pokemongo_bot/event_handlers/telegram_handler.py index b32a083225..4052e8c4ea 100644 --- a/pokemongo_bot/event_handlers/telegram_handler.py +++ b/pokemongo_bot/event_handlers/telegram_handler.py @@ -21,10 +21,22 @@ def handle_event(self, event, sender, level, formatted_msg, data): if event == 'level_up': msg = "level up ({})".format(data["current_level"]) elif event == 'pokemon_caught': - if data["pokemon"] in self.pokemons or self.pokemons[0]=="all": - msg = "Caught {} CP: {}, IV: {}".format(data["pokemon"],data["cp"],data["iv"]) + if isinstance(self.pokemons, list): + if data["pokemon"] in self.pokemons or "all" in self.pokemons: + msg = "Caught {} CP: {}, IV: {}".format(data["pokemon"],data["cp"],data["iv"]) + else: + return else: - return + if data["pokemon"] in self.pokemons: + trigger = self.pokemons[data["pokemon"]] + elif "all" in self.pokemons: + trigger = self.pokemons["all"] + else: + return + if (not "operator" in trigger or trigger["operator"] == "and") and data["cp"] >= trigger["cp"] and data["iv"] >= trigger["iv"] or ("operator" in trigger and trigger["operator"] == "or" and (data["cp"] >= trigger["cp"] or data["iv"] >= trigger["iv"])): + msg = "Caught {} CP: {}, IV: {}".format(data["pokemon"],data["cp"],data["iv"]) + else: + return else: return self.tbot.sendMessage(chat_id=master, parse_mode='Markdown', text=msg) From ab9fce62b4631a506f227a1dbca2c244010d1516 Mon Sep 17 00:00:00 2001 From: DeXtroTip Date: Mon, 29 Aug 2016 14:39:52 +0100 Subject: [PATCH 13/23] Incubate eggs fix (#4881) * Fixed incubator_eggs wrong print * Fixed pokemon hatched from eggs not added to cached inventory * Fix * Fixed not using breakable incubators * Fixed error adding pokemon to cached inventory * Moved remove egg and add Pokemon to _hatch_eggs --- pokemongo_bot/cell_workers/incubate_eggs.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/pokemongo_bot/cell_workers/incubate_eggs.py b/pokemongo_bot/cell_workers/incubate_eggs.py index 10121c36cd..2bf147d5ab 100644 --- a/pokemongo_bot/cell_workers/incubate_eggs.py +++ b/pokemongo_bot/cell_workers/incubate_eggs.py @@ -1,5 +1,6 @@ from datetime import datetime, timedelta +from pokemongo_bot import inventory from pokemongo_bot.human_behaviour import sleep from pokemongo_bot.base_task import BaseTask @@ -35,15 +36,18 @@ def work(self): except: return + should_print = self._should_print() + 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() + should_print = False else: self.bot.metrics.next_hatching_km(km_left) - if self._should_print(): + if should_print: self._print_eggs() self._compute_next_update() @@ -58,9 +62,9 @@ def work(self): if self.ready_breakable_incubators: # get available eggs - eggs = self._filter_sort_eggs(self.infinite_incubator, - self.infinite_longer_eggs_first) - self._apply_incubators(eggs, self.ready_infinite_incubators) + eggs = self._filter_sort_eggs(self.breakable_incubator, + self.breakable_longer_eggs_first) + self._apply_incubators(eggs, self.ready_breakable_incubators) def _filter_sort_eggs(self, allowed, sorting): @@ -203,13 +207,15 @@ def _hatch_eggs(self): 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.get('pokemon_id')-1)]['Name'] + #remove as egg and add as pokemon + inventory.pokemons().remove(pokemon['id']) + inventory.pokemons().add(inventory.Pokemon(pokemon)) else: pokemon['name'] = "error" except: From 8ebbbe8d8ee1adab6af48fac64fec7f60de2cfc3 Mon Sep 17 00:00:00 2001 From: Ingwar Wirjawan Date: Mon, 29 Aug 2016 22:10:07 +0700 Subject: [PATCH 14/23] add some sanitycheck (#4891) --- pokemongo_bot/cell_workers/evolve_pokemon.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pokemongo_bot/cell_workers/evolve_pokemon.py b/pokemongo_bot/cell_workers/evolve_pokemon.py index 431633aab8..7cd37f77bc 100644 --- a/pokemongo_bot/cell_workers/evolve_pokemon.py +++ b/pokemongo_bot/cell_workers/evolve_pokemon.py @@ -40,7 +40,7 @@ def work(self): if self.evolve_all[0] != 'all': # check for negation - negate = filter(lambda x: x[0] == '-', self.evolve_all) + negate = filter(lambda x: len(x) > 0 and x[0] == '-', self.evolve_all) # if there are things to negate if len(negate) > 0: From 028f3969c23fda443f6ba4c93b82c2601b16f7b4 Mon Sep 17 00:00:00 2001 From: Ingwar Wirjawan Date: Mon, 29 Aug 2016 22:18:33 +0700 Subject: [PATCH 15/23] execute setup.sh -u if there is a need to (#4870) * execute setup.sh -u if there is a need to * ask the user whether to run setup.sh -u or not * fix grammatical error --- run.sh | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/run.sh b/run.sh index c0179885a5..5f8d199bee 100755 --- a/run.sh +++ b/run.sh @@ -3,38 +3,39 @@ pokebotpath=$(cd "$(dirname "$0")"; pwd) auth="" config="" if [ ! -z $1 ]; then -auth=$1 + auth=$1 else -auth="./configs/auth.json" + auth="./configs/auth.json" fi if [ ! -z $2 ]; then -config=$2 + config=$2 else -config="./configs/config.json" + config="./configs/config.json" fi cd $pokebotpath source bin/activate git fetch -a -if [ "1" == $(git branch -vv |grep -c "* dev") ] && [ $(git log --pretty=format:"%h" -1) != $(git log --pretty=format:"%h" -1 origin/dev) ] +if [ "1" == $(git branch -vv |grep -c "* dev") ] && [ $(git log --pretty=format:"%h" -1) != $(git log --pretty=format:"%h" -1 origin/dev) ] || + [ "1" == $(git branch -vv |grep -c "* master") ] && [ $(git log --pretty=format:"%h" -1) != $(git log --pretty=format:"%h" -1 origin/master) ] then -echo "Branch dev have an update. Run ./setup.sh -u to update." -sleep 2 -elif [ "1" == $(git branch -vv |grep -c "* master") ] && [ $(git log --pretty=format:"%h" -1) != $(git log --pretty=format:"%h" -1 origin/master) ] -then -echo "Branch master have an update. Run ./setup.sh -u to update." -sleep 2 + read -p "Branch has an update. Run ./setup.sh -u to update? y/n + " do_setup + if [[ $do_setup = "y" || $do_setup = "Y" ]]; + then + ./setup.sh -u + fi fi if [ ! -f "$auth" ]; then -echo "There's no auth file. Please use ./setup.sh -a to create one" + echo "There's no auth file. Please use ./setup.sh -a to create one" fi if [ ! -f "$config" ]; then -echo "There's no config file. Please use ./setup.sh -c to create one." + echo "There's no config file. Please use ./setup.sh -c to create one." fi while true do -python pokecli.py -af $auth -cf $config -echo `date`" Pokebot "$*" Stopped." -read -p "Press any button or wait 20 seconds to continue. -" -r -s -n1 -t 20 + python pokecli.py -af $auth -cf $config + echo `date`" Pokebot "$*" Stopped." + read -p "Press any button or wait 20 seconds to continue. + " -r -s -n1 -t 20 done exit 0 From 7d281ba5d1f9464f6c936e1c8e7d5f57da6195a3 Mon Sep 17 00:00:00 2001 From: walkerlee Date: Mon, 29 Aug 2016 23:20:01 +0800 Subject: [PATCH 16/23] Add PokemonGo bot version to docker image (#4886) * fix pep8 * Add PokemonGo bot version to docker image * Use https://api.github.com/repos/PokemonGoF/PokemonGo-Bot/commits{/sha} API * Fix remove pyc, pyo files --- .gitignore | 2 ++ Dockerfile | 19 +++++++++--------- pokecli.py | 57 +++++++++++++++++++++++++++++++++--------------------- 3 files changed, 47 insertions(+), 31 deletions(-) diff --git a/.gitignore b/.gitignore index 2f1e1c6ffd..c73247b631 100644 --- a/.gitignore +++ b/.gitignore @@ -113,7 +113,9 @@ data/map-caught-*.json data/recent-forts-*.json data/caught-*.json data/deviceid-*.txt +data/mqtt_client_id.* user_web_catchable +version # Multiple config configs/* diff --git a/Dockerfile b/Dockerfile index 0b8b72626d..cd3830b7fd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -25,27 +25,28 @@ VOLUME ["/usr/src/app/configs", "/usr/src/app/web"] ADD https://raw.githubusercontent.com/$BUILD_REPO/$BUILD_BRANCH/requirements.txt . RUN apk -U --no-cache add python py-pip \ - && apk --no-cache add --virtual .build-dependencies python-dev gcc make musl-dev git tzdata tar \ + && apk --no-cache add --virtual .build-dependencies python-dev gcc make musl-dev git tzdata \ && cp -fa /usr/share/zoneinfo/$TIMEZONE /etc/localtime \ && echo $TIMEZONE > /etc/timezone \ && ln -s locale.h /usr/include/xlocale.h \ && pip install --no-cache-dir -r requirements.txt \ && apk del .build-dependencies \ && rm -rf /var/cache/apk/* /usr/include/xlocale.h \ - && find / -name '*.pyc' -o -name '*.pyo' -exec rm -f {} \; + && find / -name '*.pyc' -o -name '*.pyo' | xargs -rn1 rm -f ADD http://pgoapi.com/pgoencrypt.tar.gz /tmp/pgoencrypt.tar.gz -RUN apk --no-cache add --virtual .pgoencrypt-dependencies gcc make musl-dev tar \ - && cat /tmp/pgoencrypt.tar.gz | tar xzf - -C /tmp \ +RUN apk --no-cache add --virtual .pgoencrypt-dependencies gcc make musl-dev \ + && tar zxf /tmp/pgoencrypt.tar.gz -C /tmp \ && make -C /tmp/pgoencrypt/src \ && cp /tmp/pgoencrypt/src/libencrypt.so /usr/src/app/encrypt.so \ && apk del .pgoencrypt-dependencies \ && rm -rf /var/cache/apk/* /tmp/pgoencrypt /tmp/pgoencrypt.tar.gz -ADD https://github.com/$BUILD_REPO/archive/$BUILD_BRANCH.tar.gz /tmp -RUN apk -U --no-cache add --virtual .tar-deps tar \ - && cat /tmp/$BUILD_BRANCH.tar.gz | tar -zxf - --strip-components=1 -C /usr/src/app \ - && apk del .tar-deps \ - && rm /tmp/$BUILD_BRANCH.tar.gz +ADD https://api.github.com/repos/$BUILD_REPO/commits/$BUILD_BRANCH /tmp/pgobot-version +RUN apk -U --no-cache add --virtual .pgobot-dependencies wget ca-certificates tar jq \ + && wget -q -O- https://github.com/$BUILD_REPO/archive/$BUILD_BRANCH.tar.gz | tar zxf - --strip-components=1 -C /usr/src/app \ + && jq -r .sha /tmp/pgobot-version > /usr/src/app/version \ + && apk del .pgobot-dependencies \ + && rm -rf /var/cache/apk/* /tmp/pgobot-version ENTRYPOINT ["python", "pokecli.py"] diff --git a/pokecli.py b/pokecli.py index 6598efca34..e81cf239f6 100644 --- a/pokecli.py +++ b/pokecli.py @@ -38,12 +38,10 @@ import signal import string import subprocess -from datetime import timedelta from getpass import getpass from pgoapi.exceptions import NotLoggedInException, ServerSideRequestThrottlingException, ServerBusyOrOfflineException, NoPlayerPositionSetException from geopy.exc import GeocoderQuotaExceeded -from pokemongo_bot import inventory from pokemongo_bot import PokemonGoBot, TreeConfigBuilder from pokemongo_bot.base_dir import _base_dir from pokemongo_bot.health_record import BotEvent @@ -65,7 +63,10 @@ logger = logging.getLogger('cli') logger.setLevel(logging.INFO) -class SIGINTRecieved(Exception): pass + +class SIGINTRecieved(Exception): + pass + def main(): bot = False @@ -81,21 +82,29 @@ def initialize_task(bot, config): def initialize(config): bot = PokemonGoBot(config) return bot - - def start_bot(bot,config): + + def start_bot(bot, config): bot.start() - initialize_task(bot,config) + initialize_task(bot, config) bot.metrics.capture_stats() bot.health_record = BotEvent(config) return bot def get_commit_hash(): try: - hash = subprocess.check_output(['git', 'rev-parse', 'HEAD'], stderr=subprocess.STDOUT)[:-1] - - return hash if all(c in string.hexdigits for c in hash) else "not found" + hash = subprocess.check_output(['git', 'rev-parse', 'HEAD'], + stderr=subprocess.STDOUT) + if all(c in string.hexdigits for c in hash[:-1]): + with open('version', 'w') as f: + f.write(hash) except: - return "not found" + pass + + if not os.path.exists('version'): + return 'unknown' + + with open('version') as f: + return f.read()[:8] try: logger.info('PokemonGO Bot v1.0') @@ -116,7 +125,7 @@ def get_commit_hash(): while not finished: try: bot = initialize(config) - bot = start_bot(bot,config) + bot = start_bot(bot, config) config_changed = check_mod(config_file) bot.event_manager.emit( @@ -132,10 +141,11 @@ def get_commit_hash(): logger.info('Config changed! Applying new config.') config, _ = init_config() - if config.live_config_update_tasks_only: initialize_task(bot, config) - else: + if config.live_config_update_tasks_only: + initialize_task(bot, config) + else: bot = initialize(config) - bot = start_bot(bot,config) + bot = start_bot(bot, config) except KeyboardInterrupt: bot.event_manager.emit( @@ -232,6 +242,7 @@ def get_commit_hash(): data={'path': cached_forts_path} ) + def check_mod(config_file): check_mod.mtime = os.path.getmtime(config_file) @@ -245,6 +256,7 @@ def compare_mtime(): return compare_mtime + def report_summary(bot): if bot.metrics.start_time is None: return # Bot didn't actually start, no metrics to show. @@ -270,6 +282,7 @@ def report_summary(bot): if metrics.most_perfect is not None: logger.info('Most Perfect Pokemon: {}'.format(metrics.most_perfect['desc'])) + def init_config(): parser = argparse.ArgumentParser() config_file = os.path.join(_base_dir, 'configs', 'config.json') @@ -390,8 +403,7 @@ def _json_loader(filename): load, short_flag="-wmax", long_flag="--walk_max", - help= - "Walk instead of teleport with given speed", + help="Walk instead of teleport with given speed", type=float, default=2.5 ) @@ -400,8 +412,7 @@ def _json_loader(filename): load, short_flag="-wmin", long_flag="--walk_min", - help= - "Walk instead of teleport with given speed", + help="Walk instead of teleport with given speed", type=float, default=2.5 ) @@ -632,7 +643,7 @@ def _json_loader(filename): type=bool, default=False ) - + # Start to parse other attrs config = parser.parse_args() if not config.username and 'username' not in load: @@ -697,10 +708,10 @@ def task_configuration_error(flag_name): if "daily_catch_limit" in load: logger.warning('The daily_catch_limit argument has been moved into the CatchPokemon Task') - + if "logging_color" in load: logger.warning('The logging_color argument has been moved into the logging config section') - + if config.walk_min < 1: parser.error("--walk_min is out of range! (should be >= 1.0)") return None @@ -727,6 +738,7 @@ def task_configuration_error(flag_name): fix_nested_config(config) return config, config_file + def add_config(parser, json_config, short_flag=None, long_flag=None, **kwargs): if not long_flag: raise Exception('add_config calls requires long_flag parameter!') @@ -734,7 +746,7 @@ def add_config(parser, json_config, short_flag=None, long_flag=None, **kwargs): full_attribute_path = long_flag.split('--')[1] attribute_name = full_attribute_path.split('.')[-1] - if '.' in full_attribute_path: # embedded config! + if '.' in full_attribute_path: # embedded config! embedded_in = full_attribute_path.split('.')[0: -1] for level in embedded_in: json_config = json_config.get(level, {}) @@ -757,6 +769,7 @@ def fix_nested_config(config): config_dict[new_key] = value del config_dict[key] + def parse_unicode_str(string): try: return string.decode('utf8') From a18b3e219b536db17db6cd58357f4b4637357a42 Mon Sep 17 00:00:00 2001 From: Nikolay Spiridonov Date: Mon, 29 Aug 2016 21:24:40 +0400 Subject: [PATCH 17/23] Call level_up_rewards on exp changes/Some pep-8 (#4896) * Call level_up_rewards on exp changes. * Cleanup --- pokemongo_bot/__init__.py | 78 ++++++-------- .../cell_workers/collect_level_up_reward.py | 25 +---- pokemongo_bot/inventory.py | 101 +++++++++++++++--- pokemongo_bot/metrics.py | 8 +- 4 files changed, 123 insertions(+), 89 deletions(-) diff --git a/pokemongo_bot/__init__.py b/pokemongo_bot/__init__.py index 03d76dc7ce..8a332fcd6d 100644 --- a/pokemongo_bot/__init__.py +++ b/pokemongo_bot/__init__.py @@ -36,14 +36,16 @@ from pokemongo_bot.datastore import _init_database, Datastore from worker_result import WorkerResult from tree_config_builder import ConfigException, MismatchTaskApiVersion, TreeConfigBuilder -from inventory import init_inventory +from inventory import init_inventory, player from sys import platform as _platform from pgoapi.protos.POGOProtos.Enums import BadgeType_pb2 import struct + class FileIOException(Exception): pass + class PokemonGoBot(Datastore): @property def position(self): @@ -121,7 +123,9 @@ 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() + if self.sleep_schedule: + self.sleep_schedule.work() + self._setup_api() self._load_recent_forts() init_inventory(self) @@ -186,7 +190,7 @@ def _register_events(self): 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 @@ -198,11 +202,6 @@ def _register_events(self): 'threshold' ) ) - - - - - self.event_manager.register_event( 'position_update', parameters=( @@ -227,7 +226,6 @@ def _register_events(self): ) ) - self.event_manager.register_event('location_cache_error') self.event_manager.register_event('bot_start') @@ -601,9 +599,6 @@ def _register_events(self): 'move_to_map_pokemon', parameters=('message') ) - - - # cached recent_forts self.event_manager.register_event('loaded_cached_forts') self.event_manager.register_event('cached_fort') @@ -636,7 +631,8 @@ def tick(self): self.health_record.heartbeat() self.cell = self.get_meta_cell() - if self.sleep_schedule: self.sleep_schedule.work() + if self.sleep_schedule: + self.sleep_schedule.work() now = time.time() * 1000 @@ -774,7 +770,7 @@ def _setup_logging(self): 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']: @@ -782,7 +778,7 @@ def _setup_logging(self): 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) @@ -828,7 +824,7 @@ def login(self): 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? + self.api.set_position(lat, lng, self.alt) # or should the alt kept to zero? while not self.api.login( self.config.auth_service, @@ -1098,7 +1094,7 @@ def _set_starting_position(self): level='debug', formatted='Loading cached location...' ) - + json_file = os.path.join(_base_dir, 'data', 'last-location-%s.json' % self.config.username) try: @@ -1106,7 +1102,7 @@ def _set_starting_position(self): location_json = json.load(infile) except (IOError, ValueError): # Unable to read json file. - # File may be corrupt. Create a new one. + # File may be corrupt. Create a new one. location_json = [] except: raise FileIOException("Unexpected error reading from {}".web_inventory) @@ -1220,7 +1216,7 @@ def heartbeat(self): responses = request.call() if responses['responses']['GET_PLAYER']['success'] == True: - #we get the player_data anyway, might as well store it + # 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', @@ -1230,7 +1226,7 @@ def heartbeat(self): 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 + # 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'): @@ -1245,9 +1241,9 @@ def heartbeat(self): level='info', formatted='awarded badge: {badge}, lvl {level}', data={'badge': badgename, - 'level' : badgelevel } + 'level': badgelevel} ) - human_behaviour.action_delay(3,10) + human_behaviour.action_delay(3, 10) try: self.web_update_queue.put_nowait(True) # do this outside of thread every tick @@ -1260,34 +1256,21 @@ def update_web_location_worker(self): 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) + 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)) - 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)) + 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 @@ -1322,7 +1305,6 @@ 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 diff --git a/pokemongo_bot/cell_workers/collect_level_up_reward.py b/pokemongo_bot/cell_workers/collect_level_up_reward.py index 4e80d1fb62..ff0c8c5af2 100644 --- a/pokemongo_bot/cell_workers/collect_level_up_reward.py +++ b/pokemongo_bot/cell_workers/collect_level_up_reward.py @@ -12,12 +12,12 @@ class CollectLevelUpReward(BaseTask): def initialize(self): self._process_config() - self.current_level = self._get_current_level() + self.current_level = inventory.player().level self.previous_level = 0 def work(self): if self._should_run(): - self.current_level = self._get_current_level() + self.current_level = inventory.player().level if self.collect_reward: # let's check level reward on bot initialization @@ -70,24 +70,3 @@ def _collect_level_reward(self): 'items': data } ) - - def _get_current_level(self): - level = 0 - response_dict = self.bot.api.get_inventory() - data = (response_dict - .get('responses', {}) - .get('GET_INVENTORY', {}) - .get('inventory_delta', {}) - .get('inventory_items', {})) - - for item in data: - level = (item - .get('inventory_item_data', {}) - .get('player_stats', {}) - .get('level', 0)) - - # we found a level, no need to continue iterate - if level: - break - - return level diff --git a/pokemongo_bot/inventory.py b/pokemongo_bot/inventory.py index 0459bd4d34..2a858b6c11 100644 --- a/pokemongo_bot/inventory.py +++ b/pokemongo_bot/inventory.py @@ -1,6 +1,7 @@ import json import logging import os +import time from collections import OrderedDict from pokemongo_bot.base_dir import _base_dir @@ -14,6 +15,7 @@ https://www.reddit.com/r/pokemongodev/comments/4w7mdg/combat_damage_calculation_formula_exactly/ ''' + class FileIOException(Exception): pass @@ -22,8 +24,9 @@ class FileIOException(Exception): # Abstraction class _StaticInventoryComponent(object): - STATIC_DATA_FILE = None # optionally load static data from file, - # dropping the data in a static variable named STATIC_DATA + # optionally load static data from file, + # dropping the data in a static variable named STATIC_DATA + STATIC_DATA_FILE = None STATIC_DATA = None def __init__(self): @@ -80,6 +83,64 @@ def all(self): # # Inventory Components +class Player(_BaseInventoryComponent): + TYPE = 'player_stats' + + def __init__(self, bot, ttl=3): + self.bot = bot + self._exp = None + self._level = None + self.ttl = ttl + self.next_level_xp = None + self.pokemons_captured = None + self.poke_stop_visits = None + self.last_lvl_up_reward = time.time() # ts of last lvl_up_reward api call + super(_BaseInventoryComponent, self).__init__() + + @property + def level(self): + return self._level + + @level.setter + def level(self, value): + if self._level != value: + now = time.time() + if now - self.last_lvl_up_reward > self.ttl: + self.bot.api.level_up_rewards(level=self.level) + + self._level = value + + @property + def exp(self): + return self._exp + + @exp.setter + def exp(self, value): + if self._exp != value: + now = time.time() + if now - self.last_lvl_up_reward > self.ttl: + self.bot.api.level_up_rewards(level=self.level) + + self._exp = value + + def parse(self, item): + self.exp = item['experience'] + self.level = item['level'] + self.next_level_xp = item['next_level_xp'] + self.pokemons_captured = item['pokemons_captured'] + self.poke_stop_visits = item['poke_stop_visits'] + + def retrieve_data(self, inventory): + ret = {} + for item in inventory: + data = item['inventory_item_data'] + if self.TYPE in data: + item = data[self.TYPE] + ret = item + self.parse(item) + + return ret + class Candies(_BaseInventoryComponent): TYPE = 'candy' @@ -626,6 +687,7 @@ class PokemonInfo(object): """ Static information about pokemon kind """ + def __init__(self, data): self._data = data self.id = int(data["Number"]) @@ -1108,19 +1170,21 @@ def __init__(self, bot): self.candy = Candies() self.items = Items() self.pokemons = Pokemons() + self.player = Player(bot=self.bot) # include inventory inside Player? self.refresh() self.item_inventory_size = None self.pokemon_inventory_size = None - def refresh(self): - inventory = self.bot.api.get_inventory() + def refresh(self, inventory=None): + if inventory is None: + inventory = self.bot.api.get_inventory() + inventory = inventory['responses']['GET_INVENTORY']['inventory_delta']['inventory_items'] - for i in (self.pokedex, self.candy, self.items, self.pokemons): + for i in (self.pokedex, self.candy, self.items, self.pokemons, self.player): i.refresh(inventory) self.update_web_inventory() - def init_inventory_outfile(self): web_inventory = os.path.join(_base_dir, "web", "inventory-%s.json" % self.bot.config.username) @@ -1128,23 +1192,22 @@ def init_inventory_outfile(self): self.bot.logger.info('No inventory file %s found. Creating a new one' % web_inventory) json_inventory = [] - + with open(web_inventory, "w") as outfile: json.dump(json_inventory, outfile) - def update_web_inventory(self): web_inventory = os.path.join(_base_dir, "web", "inventory-%s.json" % self.bot.config.username) if not os.path.exists(web_inventory): self.init_inventory_outfile() - + try: with open(web_inventory, "r") as infile: json_inventory = json.load(infile) except (IOError, ValueError): # Unable to read json from web inventory - # File may be corrupt. Create a new one. + # File may be corrupt. Create a new one. self.bot.logger.info('[x] Error while opening inventory file for read: %s' % e, 'red') json_inventory = [] except: @@ -1180,9 +1243,9 @@ def jsonify_inventory(self): for pokemon in self.pokemons.all(): json_inventory.append({"inventory_item_data": {"pokemon_data": pokemon._data}}) - + return json_inventory - + def retrieve_inventories_size(self): """ Retrieves the item inventory size @@ -1273,17 +1336,19 @@ def init_inventory(bot): _inventory = Inventory(bot) -def refresh_inventory(): +def refresh_inventory(data=None): """ Refreshes the cached inventory, retrieves data from the server. :return: Nothing. :rtype: None """ - _inventory.refresh() - + _inventory.refresh(data) + + def update_web_inventory(): _inventory.update_web_inventory() + def get_item_inventory_size(): """ Access to the Item inventory size. @@ -1293,6 +1358,7 @@ def get_item_inventory_size(): _inventory.retrieve_inventories_size() return _inventory.item_inventory_size + def get_pokemon_inventory_size(): """ Access to the Item inventory size. @@ -1302,6 +1368,7 @@ def get_pokemon_inventory_size(): _inventory.retrieve_inventories_size() return _inventory.pokemon_inventory_size + def pokedex(): """ @@ -1312,6 +1379,10 @@ def pokedex(): return _inventory.pokedex +def player(): + return _inventory.player + + def candies(): """ diff --git a/pokemongo_bot/metrics.py b/pokemongo_bot/metrics.py index 693a357cfd..147fbc9927 100644 --- a/pokemongo_bot/metrics.py +++ b/pokemongo_bot/metrics.py @@ -1,6 +1,6 @@ import time from datetime import timedelta -from pokemongo_bot.inventory import Pokemons +from pokemongo_bot.inventory import Pokemons, refresh_inventory class Metrics(object): @@ -24,7 +24,7 @@ def __init__(self, bot): self.uniq_pokemons_caught = None self.uniq_pokemons_list = None - + self.player_stats = [] self.inventory_data = [] @@ -107,11 +107,13 @@ def capture_stats(self): request.get_inventory() request.get_player() response_dict = request.call() + refresh_inventory(response_dict) try: uniq_pokemon_list = set() self.dust['latest'] = response_dict['responses']['GET_PLAYER']['player_data']['currencies'][1]['amount'] - if self.dust['start'] < 0: self.dust['start'] = self.dust['latest'] + if self.dust['start'] < 0: + self.dust['start'] = self.dust['latest'] for item in response_dict['responses']['GET_INVENTORY']['inventory_delta']['inventory_items']: if 'inventory_item_data' in item: From 00949181ff40299d372eb88b6e706c0d02c25411 Mon Sep 17 00:00:00 2001 From: Matt J Madsen Date: Mon, 29 Aug 2016 14:15:48 -0500 Subject: [PATCH 18/23] Improvements to evolve + config md updates (#4900) * Better do not evolve handling * Edit config * Update config * Edit config * Edit config * Edit config * Update config.json.path.example * Update config.json.map.example * Update config.json.example * Update config.json.cluster.example * Updated configuration_files.md * Add extra tests * Update config * Update config * Update config * Update config * Update config.json.pokemon.example * Update config.json.cluster.example * Begin fixing configuration_files.md * Small fix * Small fix * Bit for of config updated * Bit more on config * A few more to config md * Bit more of of an update --- configs/config.json.cluster.example | 15 +++- configs/config.json.example | 15 +++- configs/config.json.map.example | 15 +++- configs/config.json.path.example | 15 +++- configs/config.json.pokemon.example | 15 +++- docs/configuration_files.md | 75 ++++++++++++++++++-- pokemongo_bot/cell_workers/evolve_pokemon.py | 33 +++++---- 7 files changed, 144 insertions(+), 39 deletions(-) diff --git a/configs/config.json.cluster.example b/configs/config.json.cluster.example index ed7d9e6508..8ee264c2ce 100644 --- a/configs/config.json.cluster.example +++ b/configs/config.json.cluster.example @@ -95,9 +95,18 @@ "type": "EvolvePokemon", "config": { "enabled": false, - "//evolve all except Zubat and Rattata": "", - "//evolve_all": "-Zubat,-Rattata", - "evolve_all": "none", + + "// evolve only pidgey and drowzee": "", + "// evolve_list": "pidgey, drowzee", + "// donot_evolve_list": "none", + + "// evolve all but pidgey and drowzee": "", + "// evolve_list": "all", + "// donot_evolve_list": "pidgey, drowzee", + + "evolve_list": "all", + "donot_evolve_list": "none", + "first_evolve_by": "cp", "evolve_above_cp": 500, "evolve_above_iv": 0.8, diff --git a/configs/config.json.example b/configs/config.json.example index 5cb0d9c2d9..7200844586 100644 --- a/configs/config.json.example +++ b/configs/config.json.example @@ -120,9 +120,18 @@ "type": "EvolvePokemon", "config": { "enabled": false, - "//evolve all except Zubat and Rattata": "", - "//evolve_all": "-Zubat,-Rattata", - "evolve_all": "none", + + "// evolve only pidgey and drowzee": "", + "// evolve_list": "pidgey, drowzee", + "// donot_evolve_list": "none", + + "// evolve all but pidgey and drowzee": "", + "// evolve_list": "all", + "// donot_evolve_list": "pidgey, drowzee", + + "evolve_list": "all", + "donot_evolve_list": "none", + "first_evolve_by": "cp", "evolve_above_cp": 500, "evolve_above_iv": 0.8, diff --git a/configs/config.json.map.example b/configs/config.json.map.example index ee0d99834a..7efe794846 100644 --- a/configs/config.json.map.example +++ b/configs/config.json.map.example @@ -95,9 +95,18 @@ "type": "EvolvePokemon", "config": { "enabled": false, - "//evolve all except Zubat and Rattata": "", - "//evolve_all": "-Zubat,-Rattata", - "evolve_all": "none", + + "// evolve only pidgey and drowzee": "", + "// evolve_list": "pidgey, drowzee", + "// donot_evolve_list": "none", + + "// evolve all but pidgey and drowzee": "", + "// evolve_list": "all", + "// donot_evolve_list": "pidgey, drowzee", + + "evolve_list": "all", + "donot_evolve_list": "none", + "first_evolve_by": "cp", "evolve_above_cp": 500, "evolve_above_iv": 0.8, diff --git a/configs/config.json.path.example b/configs/config.json.path.example index 2e053d6d8d..efa9493505 100644 --- a/configs/config.json.path.example +++ b/configs/config.json.path.example @@ -95,9 +95,18 @@ "type": "EvolvePokemon", "config": { "enabled": false, - "//evolve all except Zubat and Rattata": "", - "//evolve_all": "-Zubat,-Rattata", - "evolve_all": "none", + + "// evolve only pidgey and drowzee": "", + "// evolve_list": "pidgey, drowzee", + "// donot_evolve_list": "none", + + "// evolve all but pidgey and drowzee": "", + "// evolve_list": "all", + "// donot_evolve_list": "pidgey, drowzee", + + "evolve_list": "all", + "donot_evolve_list": "none", + "first_evolve_by": "cp", "evolve_above_cp": 500, "evolve_above_iv": 0.8, diff --git a/configs/config.json.pokemon.example b/configs/config.json.pokemon.example index 37d3cf98e7..f2641b12ac 100644 --- a/configs/config.json.pokemon.example +++ b/configs/config.json.pokemon.example @@ -95,9 +95,18 @@ "type": "EvolvePokemon", "config": { "enabled": false, - "//evolve all except Zubat and Rattata": "", - "//evolve_all": "-Zubat,-Rattata", - "evolve_all": "none", + + "// evolve only pidgey and drowzee": "", + "// evolve_list": "pidgey, drowzee", + "// donot_evolve_list": "none", + + "// evolve all but pidgey and drowzee": "", + "// evolve_list": "all", + "// donot_evolve_list": "pidgey, drowzee", + + "evolve_list": "all", + "donot_evolve_list": "none", + "first_evolve_by": "cp", "evolve_above_cp": 500, "evolve_above_iv": 0.8, diff --git a/docs/configuration_files.md b/docs/configuration_files.md index d56b213235..ed615b0045 100644 --- a/docs/configuration_files.md +++ b/docs/configuration_files.md @@ -55,9 +55,10 @@ Document the configuration options of PokemonGo-Bot. ## Usage [[back to top](#table-of-contents)] -1. copy `config.json.example` to `config.json`. -2. Edit `config.json` and replace `auth_service`, `username`, `password`, `location` and `gmapkey` with your parameters (other keys are optional, check `Advance Configuration` below) -3. Simply launch the script with : `./run.sh` or `./pokecli.py` or `python pokecli.py -cf ./configs/config.json` if you want to specify a config file +1. copy `auth.json.example` to `auth.json`. +2. Edit `auth.json` and replace `auth_service`, `username`, `password`, `location` and `gmapkey` with your parameters (other keys are optional) +3. copy `config.json.example` to `config.json`.= +3. Simply launch the script with : `./run.sh` or './run.sh ./configs/your_auth_file.json ./configs/your_base_config_file.json' ## Advanced Configuration @@ -105,27 +106,83 @@ The behaviors of the bot are configured via the `tasks` key in the `config.json` ### Task Options: [[back to top](#table-of-contents)] * CatchPokemon - * `treat_unseen_as_vip`: Default `"true"` | Set to `"false"` to disable treating pokemons you don't have in your pokedex as VIPs. + * `enabled`: Default "true" | Enable/Disable the task. + * `treat_unseen_as_vip`: Default `"true"` | If true, treat new to dex as VIP + * `catch_visible_pokemon`: Default "true" | If enabled, attempts to catch "visible" pokemon that are reachable + * `catch_lured_pokemon`: Default "true" | If enabled, attempts to catch "lured" pokemon that are reachable + * `min_ultraball_to_keep`: Default 5 | Minimum amount of reserved ultraballs to have on hand (for VIP) + * `berry_threshold`: Default 0.35 | Catch percentage we start throwing berries + * `vip_berry_threshold`: Default 0.9 | Something similar? + * `treat_unseen_as_vip`: Default "true" | If enabled, treat new to our dex as VIP + * `daily_catch_limit`: Default 800 | How many pokemon we limit ourselves to daily + * `catch_throw_parameters`: Variable catch settings + * `excellent_rate`: 0.1 | Change of excellent throw + * `great_rate`: 0.5 | Change of excellent throw + * `nice_rate`: 0.3 | Change of nice throw + * `normal_rate`: 0.1 | Change of normal throw + * `spin_success_rate` : 0.6 | Change of using a spin throw + * `hit_rate`: 0.75 | Change of overall hit chance + `catch_simulation`: + * `flee_count`: 3 | ?? + * `flee_duration`: 2 | ?? + * `catch_wait_min`: 3 | Minimum time to wait after a catch + * `catch_wait_max`: 6 | Maximum time to wait after a catch + * `berry_wait_min`: 3 | Minimum time to wait after throwing berry + * `berry_wait_max`: 5 | Maxiumum time to wait after throwing berry + * `changeball_wait_min`: 3 | Minimum time to wait when changing balls + * `changeball_wait_max`: 5 | Maximum time to wait when changing balls + * `newtodex_wait_min`: 20 | Minimum time to wait if we caught a new type of pokemon + * `newtodex_wait_max`: 39 | Maximum time to wait if we caught a new type of pokemon * EvolvePokemon - * `evolve_all`: Default `NONE` | Set to `"all"` to evolve Pokémon if possible when the bot starts. Can also be set to individual Pokémon as well as multiple separated by a comma. e.g "Pidgey,Rattata,Weedle,Zubat" + * `enable`: Disable or enable this task. + * `evolve_all`: Default `NONE` | Depreciated. Please use evolve_list and donot_evolve_list + * `evolve_list`: Default `all` | Set to all, or specifiy different pokemon seperated by a comma + * `donot_evolve_list`: Default `none` | Pokemon seperated by comma, will be ignored from evolve_list * `min_evolve_speed`: Default `25` | Minimum seconds to wait between each evolution * `max_evolve_speed`: Default `30` | Maximum seconds to wait between each evolution - * `use_lucky_egg`: Default: `False` + * `use_lucky_egg`: Default: `False` | Only evolve if we can use a lucky egg * FollowPath + * `enable`: Disable or enable this task. * `path_mode`: Default `loop` | Set the mode for the path navigator (loop, linear or single). * `path_file`: Default `NONE` | Set the file containing the waypoints for the path navigator. * FollowSpiral + * `enable`: Disable or enable this task. + * `spin_wait_min`: Default 3 | Minimum wait time after fort spin + * `spin_wait_max`: Default 5 | Maximum wait time after fort spin * HandleSoftBan * IncubateEggs - * `longer_eggs_first`: Default `True` + * `enable`: Disable or enable this task. + * `longer_eggs_first`: Depreciated + * `infinite_longer_eggs_first`: Default `true` | Prioritize longer eggs in perminent incubators. + * `breakable_longer_eggs_first`: Default `false` | Prioritize longer eggs in breakable incubators. + * `min_interval`: Default `120` | Minimum number of seconds between incubation updates. + * `infinite`: Default `[2,5,10]` | Types of eggs to be incubated in permanent incubators. + * `breakable`: Default `[2,5,10]` | Types of eggs to be incubated in breakable incubators. * MoveToFort + * `enable`: Disable or enable this task. + * `lure_attraction`: Default `true` | Be more attracted to lured forts than non + * `lure_max_distance`: Default `2000` | Maxmimum distance lured forts influence this task + * `walker`: Default `StepWalker` | Which walker moves us + * `log_interval`: Default `5` | Log output interval * [MoveToMapPokemon](#sniping-movetolocation) * NicknamePokemon + * `enable`: Disable or enable this task. * `nickname_template`: Default `""` | See the [Pokemon Nicknaming](#pokemon-nicknaming) section for more details * `nickname_above_iv`: Default `0` | Rename pokemon which iv is highter than the value * `dont_nickname_favorite`: Default `false` | Prevents renaming of favorited pokemons * `good_attack_threshold`: Default `0.7` | Threshold for perfection of the attack in it's type *(0.0-1.0)* after which attack will be treated as good.
Used for `{fast_attack_char}`, `{charged_attack_char}`, `{attack_code}` templates * RecycleItems + * `enabled`: Default `true` | Disable or enable this task + * `min_empty_space`: Default 15 | minimum spaces before forcing transfer + * `max_balls_keep`: Default 150 | Maximum cumlative balls to keep + * `max_potions_keep`: Default 50 | Maximum cumlative potions to keep + * `max_berries_keep`: Default 70 | Maximum culative berries to keep + * `max_revives_keep`: Default 70 | Maxiumum culative revies to keep + * `recycle_wait_min`: 3 | Minimum wait time after recycling an item + * `recycle_wait_max`: 5 | Maxiumum culative revies to keep + * `recycle_force`: Default true | Enable/Disable time forced item recycling + * `recycle_force_min`: Default `00:01:00` | Minimum time to wait before forcing recycling + * `recycle_force_max`: default `00:05:00` | Maximum time to wait before forcing recycling > **NOTE:** It's highly recommended to put this task before MoveToFort and SpinFort tasks. This way you'll most likely be able to loot. * `min_empty_space`: Default `6` | Minimum empty space to keep in inventory. Once the inventory has less empty space than that amount, the recycling process is triggered. Set it to the inventory size to trigger it at every tick. @@ -139,7 +196,11 @@ The behaviors of the bot are configured via the `tasks` key in the `config.json` * `recycle_force_max`: Default `00:10:00` | Maximum time to wait before scheduling next forced recycle * SpinFort + * `enabled`: Default true | Enable for disable this task + * `spin_wait_min`: Defaut 3 | Minimum wait after spinning a fort + * `spin_wait_max`: Default 5 | Maximum wait after spinning a fort * TransferPokemon + * `enable`: Disable or enable this task. * `min_free_slot`: Default `5` | Once the pokebag has less empty slots than this amount, the transfer process is triggered. | Big values (i.e 9999) will trigger the transfer process after each catch. * UpdateLiveStats * [UpdateLiveInventory](#updateliveinventory-settings) diff --git a/pokemongo_bot/cell_workers/evolve_pokemon.py b/pokemongo_bot/cell_workers/evolve_pokemon.py index 7cd37f77bc..d1177050ac 100644 --- a/pokemongo_bot/cell_workers/evolve_pokemon.py +++ b/pokemongo_bot/cell_workers/evolve_pokemon.py @@ -15,7 +15,8 @@ def __init__(self, bot, config): def initialize(self): self.api = self.bot.api - self.evolve_all = self.config.get('evolve_all', []) + self.evolve_list = self.config.get('evolve_list', []) + self.donot_evolve_list = self.config.get('donot_evolve_list', []) self.min_evolve_speed = self.config.get('min_evolve_speed', 25) self.max_evolve_speed = self.config.get('max_evolve_speed', 30) self.first_evolve_by = self.config.get('first_evolve_by', 'cp') @@ -26,36 +27,34 @@ def initialize(self): self._validate_config() def _validate_config(self): - if isinstance(self.evolve_all, basestring): - self.evolve_all = [str(pokemon_name).strip() for pokemon_name in self.evolve_all.split(',')] + if isinstance(self.evolve_list, basestring): + self.evolve_list = [str(pokemon_name).strip() for pokemon_name in self.evolve_list.split(',')] + + if isinstance(self.donot_evolve_list, basestring): + self.donot_evolve_list = [str(pokemon_name).strip() for pokemon_name in self.donot_evolve_list.split(',')] if 'evolve_speed' in self.config: - self.logger.warning("evolve_speed is deprecated, please use instead 'min_evolve_speed' and 'max_evolved_speed'.") + self.logger.warning("evolve_speed is deprecated, instead please use 'min_evolve_speed' and 'max_evolved_speed'.") + + if 'evolve_all' in self.config: + self.logger.warning("evolve_all is deprecated, instead please use 'evolve_list' and 'donot_evolve_list'.") def work(self): if not self._should_run(): return - evolve_list = self._sort_and_filter() - - if self.evolve_all[0] != 'all': - # check for negation - negate = filter(lambda x: len(x) > 0 and x[0] == '-', self.evolve_all) + filtered_list = self._sort_and_filter() - # if there are things to negate - if len(negate) > 0: - evolve_list = filter(lambda x: '-' + x.name not in negate, evolve_list) - else: - # filter out non-listed pokemons - evolve_list = filter(lambda x: x.name in self.evolve_all, evolve_list) + if (len(self.donot_evolve_list) > 0) and self.donot_evolve_list[0] != 'none': + filtered_list = filter(lambda pokemon: pokemon.name not in donot_evolve_list, filtered_list) cache = {} - for pokemon in evolve_list: + for pokemon in filtered_list: if pokemon.can_evolve_now(): self._execute_pokemon_evolve(pokemon, cache) def _should_run(self): - if not self.evolve_all or self.evolve_all[0] == 'none': + if not self.evolve_list or self.evolve_list[0] == 'none': return False # Evolve all is used - Use Lucky egg only at the first tick From 5a70730a34b7c91d9f4b8d71260791ebff924af6 Mon Sep 17 00:00:00 2001 From: Gobberwart Date: Tue, 30 Aug 2016 10:34:25 +1000 Subject: [PATCH 19/23] Refactoring to share inventory and reduce api calls Modifications to share cached inventory and reduce overall required api calls from 4 to 1. Only remaining api call comes from heartbeat which updates the cached inventory for sanity reasons. --- pokemongo_bot/__init__.py | 2 + .../cell_workers/update_live_stats.py | 48 ++----------------- .../cell_workers/update_web_playerdata.py | 43 ----------------- pokemongo_bot/inventory.py | 33 +++++-------- pokemongo_bot/metrics.py | 9 ++-- 5 files changed, 23 insertions(+), 112 deletions(-) delete mode 100644 pokemongo_bot/cell_workers/update_web_playerdata.py diff --git a/pokemongo_bot/__init__.py b/pokemongo_bot/__init__.py index 8a332fcd6d..dee1c7666d 100644 --- a/pokemongo_bot/__init__.py +++ b/pokemongo_bot/__init__.py @@ -1244,6 +1244,8 @@ def heartbeat(self): '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 diff --git a/pokemongo_bot/cell_workers/update_live_stats.py b/pokemongo_bot/cell_workers/update_live_stats.py index cfc84c0a37..c3fc479714 100644 --- a/pokemongo_bot/cell_workers/update_live_stats.py +++ b/pokemongo_bot/cell_workers/update_live_stats.py @@ -10,13 +10,11 @@ from pokemongo_bot.worker_result import WorkerResult from pokemongo_bot.tree_config_builder import ConfigException from pokemongo_bot.base_dir import _base_dir +from pokemongo_bot import inventory # XP file import json -class FileIOException(Exception): - pass - class UpdateLiveStats(BaseTask): """ Periodically displays stats about the bot in the terminal and/or in its title. @@ -163,14 +161,12 @@ def work(self): if not self._should_display(): return WorkerResult.SUCCESS - player_stats = self._get_player_stats() + player_stats = inventory.player().player_stats line = self._get_stats_line(player_stats) # If line is empty, it couldn't be generated. if not line: return WorkerResult.SUCCESS - - self.update_web_stats(player_stats) - + if self.terminal_title: self._update_title(line, _platform) @@ -209,7 +205,7 @@ def _log_on_terminal(self, stats): formatted="{stats}", data={ 'stats': stats, - 'stats_raw': self._get_stats(self._get_player_stats()) + 'stats_raw': self._get_stats(inventory.player().player_stats) } ) self._compute_next_update() @@ -416,39 +412,3 @@ def get_stat(stat): line = ' | '.join(map(get_stat, self.displayed_stats)) return line - - def _get_player_stats(self): - """ - Helper method parsing the bot inventory object and returning the player stats object. - :return: The player stats object. - :rtype: dict - """ - # TODO : find a better solution than calling the api - return self.bot.metrics.player_stats - - def update_web_stats(self,player_data): - web_inventory = os.path.join(_base_dir, "web", "inventory-%s.json" % self.bot.config.username) - - try: - with open(web_inventory, "r") as infile: - json_stats = json.load(infile) - except (IOError, ValueError): - # Unable to read json from web inventory - # File may be corrupt. Create a new one. - self.bot.logger.info('[x] Error while opening inventory file for read: %s' % e, 'red') - json_stats = [] - except: - raise FileIOException("Unexpected error loading information from json.") - - json_stats = [x for x in json_stats if not x.get("inventory_item_data", {}).get("player_stats", None)] - - json_stats.append({"inventory_item_data": {"player_stats": player_data}}) - - try: - with open(web_inventory, "w") as outfile: - json.dump(json_stats, outfile) - except (IOError, ValueError): - self.bot.logger.info('[x] Error while opening inventory file for write: %s' % e, 'red') - pass - except: - raise FileIOException("Unexpected error writing to {}".web_inventory) diff --git a/pokemongo_bot/cell_workers/update_web_playerdata.py b/pokemongo_bot/cell_workers/update_web_playerdata.py deleted file mode 100644 index 5a50fed59a..0000000000 --- a/pokemongo_bot/cell_workers/update_web_playerdata.py +++ /dev/null @@ -1,43 +0,0 @@ -import json -import os -from pokemongo_bot.base_task import BaseTask -from pokemongo_bot.base_dir import _base_dir - - -class UpdateWebPlayerdata(BaseTask): - SUPPORTED_TASK_API_VERSION = 1 - - def initialize(self): - pass - - def work(self): - self.bot.metrics.capture_stats() - self.update_player_stats(self.bot.metrics.player_stats) - - def update_player_stats(self,player_data): - web_inventory = os.path.join(_base_dir, "web", "inventory-%s.json" % self.bot.config.username) - - try: - with open(web_inventory, "r") as infile: - json_stats = json.load(infile) - except (IOError, ValueError): - # Unable to read json from web inventory - # File may be corrupt. Create a new one. - self.bot.logger.info('[x] Error while opening inventory file for read: %s' % e, 'red') - json_stats = [] - except: - raise FileIOException("Unexpected error loading information from json.") - - json_stats = [x for x in json_stats if not x.get("inventory_item_data", {}).get("player_stats", None)] - - json_stats.append({"inventory_item_data": {"player_stats": player_data}}) - - try: - with open(web_inventory, "w") as outfile: - json.dump(json_stats, outfile) - except (IOError, ValueError): - self.bot.logger.info('[x] Error while opening inventory file for write: %s' % e, 'red') - pass - except: - raise FileIOException("Unexpected error writing to {}".web_inventory) - diff --git a/pokemongo_bot/inventory.py b/pokemongo_bot/inventory.py index 2a858b6c11..621552b45b 100644 --- a/pokemongo_bot/inventory.py +++ b/pokemongo_bot/inventory.py @@ -95,6 +95,7 @@ def __init__(self, bot, ttl=3): self.pokemons_captured = None self.poke_stop_visits = None self.last_lvl_up_reward = time.time() # ts of last lvl_up_reward api call + self.player_stats = None super(_BaseInventoryComponent, self).__init__() @property @@ -123,6 +124,9 @@ def exp(self, value): self._exp = value + def refresh(self,inventory): + self.player_stats = self.retrieve_data(inventory) + def parse(self, item): self.exp = item['experience'] self.level = item['level'] @@ -138,7 +142,6 @@ def retrieve_data(self, inventory): item = data[self.TYPE] ret = item self.parse(item) - return ret @@ -1170,7 +1173,7 @@ def __init__(self, bot): self.candy = Candies() self.items = Items() self.pokemons = Pokemons() - self.player = Player(bot=self.bot) # include inventory inside Player? + self.player = Player(self.bot) # include inventory inside Player? self.refresh() self.item_inventory_size = None self.pokemon_inventory_size = None @@ -1202,23 +1205,7 @@ def update_web_inventory(self): if not os.path.exists(web_inventory): self.init_inventory_outfile() - try: - with open(web_inventory, "r") as infile: - json_inventory = json.load(infile) - except (IOError, ValueError): - # Unable to read json from web inventory - # File may be corrupt. Create a new one. - self.bot.logger.info('[x] Error while opening inventory file for read: %s' % e, 'red') - json_inventory = [] - except: - raise FileIOException("Unexpected error reading from {}".web_inventory) - - json_inventory = [x for x in json_inventory if not x.get("inventory_item_data", {}).get("pokedex_entry", None)] - json_inventory = [x for x in json_inventory if not x.get("inventory_item_data", {}).get("candy", None)] - json_inventory = [x for x in json_inventory if not x.get("inventory_item_data", {}).get("item", None)] - json_inventory = [x for x in json_inventory if not x.get("inventory_item_data", {}).get("pokemon_data", None)] - - json_inventory = json_inventory + self.jsonify_inventory() + json_inventory = self.jsonify_inventory() try: with open(web_inventory, "w") as outfile: @@ -1231,7 +1218,9 @@ def update_web_inventory(self): def jsonify_inventory(self): json_inventory = [] - + + json_inventory.append({"inventory_item_data": {"player_stats": self.player.player_stats}}) + for pokedex in self.pokedex.all(): json_inventory.append({"inventory_item_data": {"pokedex_entry": pokedex}}) @@ -1344,7 +1333,9 @@ def refresh_inventory(data=None): """ _inventory.refresh(data) - +def jsonify_inventory(): + return _inventory.jsonify_inventory() + def update_web_inventory(): _inventory.update_web_inventory() diff --git a/pokemongo_bot/metrics.py b/pokemongo_bot/metrics.py index 147fbc9927..217629d3ca 100644 --- a/pokemongo_bot/metrics.py +++ b/pokemongo_bot/metrics.py @@ -1,6 +1,7 @@ import time from datetime import timedelta from pokemongo_bot.inventory import Pokemons, refresh_inventory +from pokemongo_bot import inventory class Metrics(object): @@ -26,7 +27,6 @@ def __init__(self, bot): self.uniq_pokemons_list = None self.player_stats = [] - self.inventory_data = [] def runtime(self): return timedelta(seconds=round(time.time() - self.start_time)) @@ -104,10 +104,9 @@ def capture_stats(self): except AttributeError: return - request.get_inventory() request.get_player() response_dict = request.call() - refresh_inventory(response_dict) + try: uniq_pokemon_list = set() @@ -115,7 +114,9 @@ def capture_stats(self): if self.dust['start'] < 0: self.dust['start'] = self.dust['latest'] - for item in response_dict['responses']['GET_INVENTORY']['inventory_delta']['inventory_items']: + inventory.refresh_inventory() + json_inventory = inventory.jsonify_inventory() + for item in json_inventory: if 'inventory_item_data' in item: if 'player_stats' in item['inventory_item_data']: playerdata = item['inventory_item_data']['player_stats'] From 3dc16f55329b5bc4a7076ea8dbfb62994c2217f7 Mon Sep 17 00:00:00 2001 From: Gobberwart Date: Tue, 30 Aug 2016 10:40:01 +1000 Subject: [PATCH 20/23] Remove import of UpdateWebPlayerdata Decided there was a better way to go with this, since both UpdateWebInventory and UpdateWebPlayerdata share the same inventory input/output, just different sections. Combined into UpdateWebInventory. --- pokemongo_bot/cell_workers/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pokemongo_bot/cell_workers/__init__.py b/pokemongo_bot/cell_workers/__init__.py index 9c9e9642a1..486dc9e1c1 100644 --- a/pokemongo_bot/cell_workers/__init__.py +++ b/pokemongo_bot/cell_workers/__init__.py @@ -21,7 +21,6 @@ from complete_tutorial import CompleteTutorial from random_pause import RandomPause from update_web_inventory import UpdateWebInventory -from update_web_playerdata import UpdateWebPlayerdata from random_alive_pause import RandomAlivePause from show_best_pokemon import ShowBestPokemon from telegram_task import TelegramTask From 1df89b4eadfc2d46af4675aaf9d0f00865dcc30d Mon Sep 17 00:00:00 2001 From: Gobberwart Date: Tue, 30 Aug 2016 10:54:50 +1000 Subject: [PATCH 21/23] Fixed conflict --- pokemongo_bot/inventory.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/pokemongo_bot/inventory.py b/pokemongo_bot/inventory.py index ec7d96981b..d4a2efc5f3 100644 --- a/pokemongo_bot/inventory.py +++ b/pokemongo_bot/inventory.py @@ -95,10 +95,7 @@ def __init__(self, bot, ttl=3): self.pokemons_captured = None self.poke_stop_visits = None self.last_lvl_up_reward = time.time() # ts of last lvl_up_reward api call -<<<<<<< HEAD self.player_stats = None -======= ->>>>>>> pokemongof/dev super(_BaseInventoryComponent, self).__init__() @property @@ -127,12 +124,9 @@ def exp(self, value): self._exp = value -<<<<<<< HEAD def refresh(self,inventory): self.player_stats = self.retrieve_data(inventory) -======= ->>>>>>> pokemongof/dev def parse(self, item): self.exp = item['experience'] self.level = item['level'] @@ -148,10 +142,7 @@ def retrieve_data(self, inventory): item = data[self.TYPE] ret = item self.parse(item) -<<<<<<< HEAD -======= ->>>>>>> pokemongof/dev return ret From 8b6cba76f8f8e85871711401255e94e1689cb2c2 Mon Sep 17 00:00:00 2001 From: Gobberwart Date: Tue, 30 Aug 2016 10:59:43 +1000 Subject: [PATCH 22/23] Import inventory added to metrics Allows metrics to use the cached inventory to retrieve player stats instead of making another api call --- pokemongo_bot/metrics.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pokemongo_bot/metrics.py b/pokemongo_bot/metrics.py index 6552c513a2..111dab9d26 100644 --- a/pokemongo_bot/metrics.py +++ b/pokemongo_bot/metrics.py @@ -1,6 +1,7 @@ import time from datetime import timedelta from pokemongo_bot.inventory import Pokemons, refresh_inventory +from pokemongo_bot import inventory class Metrics(object): From 45cae64f4c1c723e1efeb18b23c7faf80289b6ef Mon Sep 17 00:00:00 2001 From: Gobberwart Date: Tue, 30 Aug 2016 11:23:54 +1000 Subject: [PATCH 23/23] Removed api call from incubate_eggs Cached inventory should be accurate enough for this --- pokemongo_bot/cell_workers/incubate_eggs.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pokemongo_bot/cell_workers/incubate_eggs.py b/pokemongo_bot/cell_workers/incubate_eggs.py index 2bf147d5ab..5cc8e7f7e3 100644 --- a/pokemongo_bot/cell_workers/incubate_eggs.py +++ b/pokemongo_bot/cell_workers/incubate_eggs.py @@ -125,7 +125,6 @@ def _apply_incubators(self, available_eggs, available_incubators): def _check_inventory(self, lookup_ids=[]): inv = {} - response_dict = self.bot.api.get_inventory() matched_pokemon = [] temp_eggs = [] temp_used_incubators = [] @@ -134,7 +133,7 @@ def _check_inventory(self, lookup_ids=[]): inv = reduce( dict.__getitem__, ["responses", "GET_INVENTORY", "inventory_delta", "inventory_items"], - response_dict + inventory.jsonify_inventory() ) for inv_data in inv: inv_data = inv_data.get("inventory_item_data", {})