From 6c9f865a580fed226e524529eae4236ca35bcc0d Mon Sep 17 00:00:00 2001 From: Stuart Travers Date: Sun, 28 Aug 2016 16:55:35 +1000 Subject: [PATCH 01/38] Expand simple logging options (#4832) * Fix bot crash at start on permaban * Expanded logging options Added "logging" section to config, with options "color", "show_datetime", "show_process_name" and "show_log_level" * Added warning about deprecated logging_color arg * Display log message moved No point trying to use the logger before it's been initialised. Moved to init_config. * Remove milliseconds from datetime Because really, do we need that? * 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. * Documented new log options * Modified conditions again Removed unnecessary second check for config values and slightly modified parentheses as per suggestion from @mjmadsen --- configs/config.json.cluster.example | 10 ++++++---- configs/config.json.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 ++++++---- docs/configuration_files.md | 8 ++++++++ pokecli.py | 8 ++++++-- pokemongo_bot/__init__.py | 17 ++++++++++++++--- 9 files changed, 64 insertions(+), 29 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.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/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/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)] diff --git a/pokecli.py b/pokecli.py index d82320a3b4..6598efca34 100644 --- a/pokecli.py +++ b/pokecli.py @@ -632,7 +632,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: @@ -652,6 +652,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)") @@ -696,7 +697,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 d98520af94..6891da550e 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,19 @@ 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' + logging_format_options = '' + + if ('show_log_level' not in self.config.logging) or self.config.logging['show_log_level']: + logging_format = '[%(levelname)s] ' + logging_format + if ('show_process_name' not in self.config.logging) or self.config.logging['show_process_name']: + logging_format = '[%(name)10s] ' + logging_format + if ('show_datetime' not in self.config.logging) or self.config.logging['show_datetime']: + logging_format = '[%(asctime)s] ' + logging_format + logging_format_options = '%Y-%m-%d %H:%M:%S' + + formatter = Formatter(logging_format,logging_format_options) for handler in logging.root.handlers[:]: handler.setFormatter(formatter) From 2832a3702a9c433b819e172fbe5b3fb297d20a7b Mon Sep 17 00:00:00 2001 From: LuckyMe4Evers Date: Sun, 28 Aug 2016 10:50:04 +0200 Subject: [PATCH 02/38] Changed ) to } (#4845) Fixed an faulty character --- windows_bat/PokemonGo-Bot-Configurator.bat | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/windows_bat/PokemonGo-Bot-Configurator.bat b/windows_bat/PokemonGo-Bot-Configurator.bat index 29e12abc85..aff0acacb9 100644 --- a/windows_bat/PokemonGo-Bot-Configurator.bat +++ b/windows_bat/PokemonGo-Bot-Configurator.bat @@ -143,7 +143,7 @@ ECHO. ECHO. "encrypt_location": "",>>%auth% SET /p telegram="What's your telegram token? Enter for leave blank: " ECHO. "telegram_token": "%telegram%">>%auth% -ECHO.)>>%auth% +ECHO.}>>%auth% goto :eof From d896a806fc53474a12e962118154b98bb16d3337 Mon Sep 17 00:00:00 2001 From: Ingwar Wirjawan Date: Sun, 28 Aug 2016 16:36:49 +0700 Subject: [PATCH 03/38] fix incubator logic (#4848) --- pokemongo_bot/cell_workers/incubate_eggs.py | 76 ++++++++++----------- pokemongo_bot/test/incubate_eggs_test.py | 58 ++++++++++++++++ web | 2 +- 3 files changed, 94 insertions(+), 42 deletions(-) create mode 100644 pokemongo_bot/test/incubate_eggs_test.py diff --git a/pokemongo_bot/cell_workers/incubate_eggs.py b/pokemongo_bot/cell_workers/incubate_eggs.py index 76f9885018..10121c36cd 100644 --- a/pokemongo_bot/cell_workers/incubate_eggs.py +++ b/pokemongo_bot/cell_workers/incubate_eggs.py @@ -11,8 +11,8 @@ class IncubateEggs(BaseTask): def initialize(self): self.next_update = None - self.ready_incubators_breakable = [] - self.ready_incubators_infinite = [] + self.ready_breakable_incubators = [] + self.ready_infinite_incubators = [] self.used_incubators = [] self.eggs = [] self.km_walked = 0 @@ -49,39 +49,33 @@ def work(self): IncubateEggs.last_km_walked = self.km_walked - sorting = self.breakable_longer_eggs_first - self.eggs.sort(key=lambda x: x.get("km"), reverse=sorting) - if self.ready_incubators_breakable: - self._apply_incubators('breakable') + # if there is a ready infinite incubator + if self.ready_infinite_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) - sorting = self.infinite_longer_eggs_first - self.eggs.sort(key=lambda x: x.get("km"), reverse=sorting) - if self.ready_incubators_infinite: - self._apply_incubators('infinite') + 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) - def _apply_incubators(self, type_of_incubator): - if type_of_incubator == 'breakable': - temp_ready_incubators = self.ready_incubators_breakable - elif type_of_incubator == 'infinite': - temp_ready_incubators = self.ready_incubators_infinite - - for incubator in temp_ready_incubators: - if incubator.get('used', False): - continue - for egg in self.eggs: - if egg["used"] or egg["km"] == -1: - continue - km = int(egg["km"]) + def _filter_sort_eggs(self, allowed, sorting): + eligible_eggs = filter(lambda egg: int(egg["km"]) in allowed, self.eggs) + eligible_eggs.sort(key=lambda egg: egg["km"], reverse=sorting) - # test if the incubator is of type breakable - if incubator.get('uses_remaining') is not None: - if km not in self.breakable_incubator: - continue - # test if the incubator is of type infinite - else: - if km not in self.infinite_incubator: - continue + return eligible_eggs + + + def _apply_incubators(self, available_eggs, available_incubators): + + for incubator in available_incubators: + for egg in available_eggs: + if egg["used"] or egg["km"] == -1: + continue self.emit_event( 'incubate_try', @@ -131,8 +125,8 @@ def _check_inventory(self, lookup_ids=[]): matched_pokemon = [] temp_eggs = [] temp_used_incubators = [] - temp_ready_incubators_breakable = [] - temp_ready_incubators_infinite = [] + temp_ready_breakable_incubators = [] + temp_ready_infinite_incubators = [] inv = reduce( dict.__getitem__, ["responses", "GET_INVENTORY", "inventory_delta", "inventory_items"], @@ -142,8 +136,8 @@ def _check_inventory(self, lookup_ids=[]): inv_data = inv_data.get("inventory_item_data", {}) if "egg_incubators" in inv_data: temp_used_incubators = [] - temp_ready_incubators_breakable = [] - temp_ready_incubators_infinite = [] + temp_ready_breakable_incubators = [] + temp_ready_infinite_incubators = [] incubators = inv_data.get("egg_incubators", {}).get("egg_incubator",[]) if isinstance(incubators, basestring): # checking for old response incubators = [incubators] @@ -158,11 +152,11 @@ def _check_inventory(self, lookup_ids=[]): }) else: if incubator.get('uses_remaining') is not None: - temp_ready_incubators_breakable.append({ + temp_ready_breakable_incubators.append({ "id": incubator.get('id', -1) }) else: - temp_ready_incubators_infinite.append({ + temp_ready_infinite_incubators.append({ "id": incubator.get('id', -1) }) continue @@ -187,10 +181,10 @@ def _check_inventory(self, lookup_ids=[]): 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_breakable: - self.ready_incubators_breakable = temp_ready_incubators_breakable - if temp_ready_incubators_infinite: - self.ready_incubators_infinite = temp_ready_incubators_infinite + if temp_ready_breakable_incubators: + self.ready_breakable_incubators = temp_ready_breakable_incubators + if temp_ready_infinite_incubators: + self.ready_infinite_incubators = temp_ready_infinite_incubators if temp_eggs: self.eggs = temp_eggs return matched_pokemon diff --git a/pokemongo_bot/test/incubate_eggs_test.py b/pokemongo_bot/test/incubate_eggs_test.py new file mode 100644 index 0000000000..1a0099c6e2 --- /dev/null +++ b/pokemongo_bot/test/incubate_eggs_test.py @@ -0,0 +1,58 @@ +import unittest +from mock import patch +from pokemongo_bot.cell_workers.incubate_eggs import IncubateEggs + + +class IncubateEggsTestCase(unittest.TestCase): + + @patch('pokemongo_bot.PokemonGoBot') + def testFilterAndSort_AllowNone(self, mock_pokemongo_bot): + incubate_eggs = IncubateEggs(mock_pokemongo_bot, {}) + + incubate_eggs.eggs = [{"km": 2.0}, {"km": 5.0}, {"km": 5.0}] + + allowed = [] + sorting = True + + result = incubate_eggs._filter_sort_eggs(allowed, sorting) + self.assertEqual([], result) + + + @patch('pokemongo_bot.PokemonGoBot') + def testFilterAndSort_AllowSome(self, mock_pokemongo_bot): + incubate_eggs = IncubateEggs(mock_pokemongo_bot, {}) + + incubate_eggs.eggs = [{"km": 5.0}, {"km": 2.0}, {"km": 5.0}, {"km": 10.0}] + + allowed = [2, 10] + sorting = True + + result = incubate_eggs._filter_sort_eggs(allowed, sorting) + self.assertEqual([{"km": 10.0}, {"km": 2.0}], result) + + + @patch('pokemongo_bot.PokemonGoBot') + def testFilterAndSort_AllowSomeNoReverseSort(self, mock_pokemongo_bot): + incubate_eggs = IncubateEggs(mock_pokemongo_bot, {}) + + incubate_eggs.eggs = [{"km": 5.0}, {"km": 2.0}, {"km": 5.0}, {"km": 10.0}] + + allowed = [2, 10] + sorting = False + + result = incubate_eggs._filter_sort_eggs(allowed, sorting) + self.assertEqual([{"km": 2.0}, {"km": 10.0}], result) + + + + @patch('pokemongo_bot.PokemonGoBot') + def testFilterAndSort_AllowAll(self, mock_pokemongo_bot): + incubate_eggs = IncubateEggs(mock_pokemongo_bot, {}) + + incubate_eggs.eggs = [{"km": 5.0}, {"km": 2.0}, {"km": 5.0}] + + allowed = [2, 5, 10] + sorting = True + + result = incubate_eggs._filter_sort_eggs(allowed, sorting) + self.assertEqual([{"km": 5.0}, {"km": 5.0}, {"km": 2.0}], result) diff --git a/web b/web index 607397a13f..6ba5609c61 160000 --- a/web +++ b/web @@ -1 +1 @@ -Subproject commit 607397a13f344c0fdc44bd9961332c1efe205de9 +Subproject commit 6ba5609c6151507b5b832a74e471b6b7b1a182c9 From af985e8fa9984d521d53f1c9d61f589ab783429f Mon Sep 17 00:00:00 2001 From: Dmitri Barski Date: Sun, 28 Aug 2016 13:18:12 +0200 Subject: [PATCH 04/38] corrected logic to respect snipe = true --- pokemongo_bot/cell_workers/move_to_map_pokemon.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pokemongo_bot/cell_workers/move_to_map_pokemon.py b/pokemongo_bot/cell_workers/move_to_map_pokemon.py index a18a77545d..9b57c99109 100644 --- a/pokemongo_bot/cell_workers/move_to_map_pokemon.py +++ b/pokemongo_bot/cell_workers/move_to_map_pokemon.py @@ -144,7 +144,7 @@ def get_pokemon_from_social(self): pokemon['longitude'], ) - if pokemon['dist'] > self.config['max_distance'] or not self.config['snipe']: + if pokemon['dist'] > self.config['max_distance'] and not self.config['snipe']: continue # pokemon not reachable with mean walking speed (by config) From 6acdc534bbb605dc1e392b10f4ec2b2c941bb75b Mon Sep 17 00:00:00 2001 From: Alexander Skovpen Date: Sun, 28 Aug 2016 15:25:05 +0400 Subject: [PATCH 05/38] Update configuration_files.md (#4854) --- docs/configuration_files.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/docs/configuration_files.md b/docs/configuration_files.md index 072b4b429d..0c98209ca4 100644 --- a/docs/configuration_files.md +++ b/docs/configuration_files.md @@ -46,6 +46,7 @@ - [Random Pause](#random-pause) - [Egg Incubator](#egg-incubator) - [ShowBestPokemon](#showbestpokemon) +- [Telegram Task](#telegram-task) #Configuration files @@ -912,3 +913,34 @@ Available `info_to_show` : ``` 2016-08-25 21:20:59,642 [ShowBestPokemon] [INFO] [show_best_pokemon] [Tauros, CP 575, IVCP 0.95, DPS 12.04] | [Grimer, CP 613, IVCP 0.93, DPS 13.93] | [Tangela, CP 736, IVCP 0.93, DPS 14.5] | [Staryu, CP 316, IVCP 0.92, DPS 10.75] | [Gastly, CP 224, IVCP 0.9, DPS 11.7] ``` + +## Telegram Task +[[back to top](#table-of-contents)] + +### Description +[[back to top](#table-of-contents)] + +[Telegram bot](https://telegram.org/) Announcer Level up, pokemon cought + +Bot answer on command '/info' self stats. + +### Options + +* `telegram_token` : bot token (getting [there](https://core.telegram.org/bots#6-botfather) - one token per bot) +* `master` : id (without quotes) or username (in quotes, first character @) of bot owner, who will gett announces. +* `alert_catch` : array of pokemons, which will be announced on catch. if first array item `all` - announce all pokemons. + +### Sample configuration +[[back to top](#table-of-contents)] +```json +{ + "type": "TelegramTask", + "config": { + "enabled": true, + "master": 12345678, + "//master": "@username", + "alert_catch": ["Lapras","Dragonite"], + "//alert_catch": ["all"] + } +} +``` From 5a1e24672df12764e06de9627fb0dbb159044925 Mon Sep 17 00:00:00 2001 From: DBa2016 Date: Sun, 28 Aug 2016 14:15:34 +0200 Subject: [PATCH 06/38] corrected logic to respect snipe = true (#4855) --- pokemongo_bot/cell_workers/move_to_map_pokemon.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pokemongo_bot/cell_workers/move_to_map_pokemon.py b/pokemongo_bot/cell_workers/move_to_map_pokemon.py index a18a77545d..9b57c99109 100644 --- a/pokemongo_bot/cell_workers/move_to_map_pokemon.py +++ b/pokemongo_bot/cell_workers/move_to_map_pokemon.py @@ -144,7 +144,7 @@ def get_pokemon_from_social(self): pokemon['longitude'], ) - if pokemon['dist'] > self.config['max_distance'] or not self.config['snipe']: + if pokemon['dist'] > self.config['max_distance'] and not self.config['snipe']: continue # pokemon not reachable with mean walking speed (by config) From 4dd28e345b36ff897fa453e8d62fd39714ade1c1 Mon Sep 17 00:00:00 2001 From: Matt J Madsen Date: Sun, 28 Aug 2016 08:32:56 -0500 Subject: [PATCH 07/38] Revert "corrected logic to respect snipe = true" (#4857) --- pokemongo_bot/cell_workers/move_to_map_pokemon.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pokemongo_bot/cell_workers/move_to_map_pokemon.py b/pokemongo_bot/cell_workers/move_to_map_pokemon.py index 9b57c99109..a18a77545d 100644 --- a/pokemongo_bot/cell_workers/move_to_map_pokemon.py +++ b/pokemongo_bot/cell_workers/move_to_map_pokemon.py @@ -144,7 +144,7 @@ def get_pokemon_from_social(self): pokemon['longitude'], ) - if pokemon['dist'] > self.config['max_distance'] and not self.config['snipe']: + if pokemon['dist'] > self.config['max_distance'] or not self.config['snipe']: continue # pokemon not reachable with mean walking speed (by config) From f1fe9421ac3da4d22096a723ff1df5cd8e807582 Mon Sep 17 00:00:00 2001 From: nivong Date: Sun, 28 Aug 2016 15:39:16 +0200 Subject: [PATCH 08/38] dont forget to update the docs when adding config changes... (#4856) * dont forget to update the docs when adding config changes... * reflect config changes.... --- docs/configuration_files.md | 2 +- docs/manual_installation.md | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/configuration_files.md b/docs/configuration_files.md index 0c98209ca4..1068ab8d57 100644 --- a/docs/configuration_files.md +++ b/docs/configuration_files.md @@ -86,7 +86,7 @@ Document the configuration options of PokemonGo-Bot. |`favorite_locations` | [] | Allows you to define a collection of locations and coordinates, allowing rapid switch using a "label" on your location config | `live_config_update.enabled` | false | Enable live config update | `live_config_update.tasks_only` | false | True: quick update for Tasks only (without re-login). False: slower update for entire config file. - +| `enable_social` | true | True: to chat with other pokemon go bot users [more information](https://github.com/PokemonGoF/PokemonGo-Bot/pull/4596) ## Logging configuration [[back to top](#table-of-contents)] diff --git a/docs/manual_installation.md b/docs/manual_installation.md index ceaba14ab9..cfeec61bc2 100644 --- a/docs/manual_installation.md +++ b/docs/manual_installation.md @@ -67,6 +67,8 @@ mv pgoencrypt/src/libencrypt.so encrypt.so ```bash cp configs/config.json.example configs/config.json vi configs/config.json +cp configs/auth.json.example configs/auth.json +vi configs/auth.json ``` #### - make shure your git repo is up to date @@ -78,7 +80,7 @@ pip install -r requirements.txt #### - finaly start the bot ```bash -./run.sh configs/config.json +./run.sh ``` #### - after reboot or closing the terminal at every new start go into the folder of the PokemonGo-Bot by going into the folder where you startet installing it an then @@ -86,7 +88,7 @@ pip install -r requirements.txt cd PokemonGo-Bot #activate virtualenv and start source bin/activate -./run.sh configs/config.json +./run.sh ``` From f6775cc9645481baffb377ac746064e7cdb18f54 Mon Sep 17 00:00:00 2001 From: nivong Date: Sun, 28 Aug 2016 16:02:55 +0200 Subject: [PATCH 09/38] please keep this as is (#4859) Add stuff in the right order. --- README.md | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 2e4bb63f80..a64e5774ea 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,23 @@ # PokemonGo-Bot PokemonGo bot is a project created by the [PokemonGoF](https://github.com/PokemonGoF) team. +## Table of Contents +- [Installation](https://github.com/PokemonGoF/PokemonGo-Bot/blob/dev/docs/installation.md) +- [Documentation](https://github.com/PokemonGoF/PokemonGo-Bot/blob/dev/docs/) +- [Support](#support) + - [help](#configuration-issueshelp) + - [bugs](#bugs--issues) + - [Feature request](#feature-requests) + - [Pull Requests](#pull-requests) +- [Features](#features) +- [Credits](#credits) + The project is currently setup in two main branches. - `dev` also known as `beta` - This is where the latest features are, but you may also experience some issues with stability/crashes - `master` also known as `stable` - The bot 'should' be stable on this branch, and is generally well tested -## [Desktop version](https://github.com/PokemonGoF/PokemonGo-Bot-Desktop) - ## Support -###Configuration issues/help +### Configuration issues/help If you need any help please don't create an issue as we have a great community on Slack. You can count on the community in [#help](https://pokemongo-bot.slack.com/messages/help/) channel. - [Click here to signup (first time only)](https://pokemongo-bot.herokuapp.com) - [Join if you're already a member](https://pokemongo-bot.slack.com/messages/general/). @@ -23,12 +32,6 @@ While you're there vote on other feature requests to let the devs know what is m ###[Pull Requests](https://github.com/PokemonGoF/PokemonGo-Bot/pulls) If you'd like to make your own changes, make sure you follow the pull request template, and ensure your PR is made against the 'dev' branch -## Table of Contents -- [Installation](https://github.com/PokemonGoF/PokemonGo-Bot/blob/dev/docs/installation.md) -- [Documentation](https://github.com/PokemonGoF/PokemonGo-Bot/blob/dev/docs/) -- [Features](#features) -- [Credits](#credits) - ## Features - [x] GPS Location configuration - [x] Search Pokestops From 1b90f3fad4023a6dd0466c350ebd9c92bd355919 Mon Sep 17 00:00:00 2001 From: Jasperrr91 Date: Sun, 28 Aug 2016 17:06:54 +0200 Subject: [PATCH 10/38] Clarify Max_distance for Sniping (#4858) * Clarify Max_distance * Added distance unit and updated configuration_files.md --- configs/config.json.map.example | 1 + docs/configuration_files.md | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/configs/config.json.map.example b/configs/config.json.map.example index ea85fe44ed..ee0d99834a 100644 --- a/configs/config.json.map.example +++ b/configs/config.json.map.example @@ -180,6 +180,7 @@ "config": { "enabled": true, "address": "http://localhost:5000", + "//NOTE: Change the max_distance to adjust the max sniping range (km)": {}, "max_distance": 500, "min_ball": 50, "prioritize_vips": true, diff --git a/docs/configuration_files.md b/docs/configuration_files.md index 1068ab8d57..d56b213235 100644 --- a/docs/configuration_files.md +++ b/docs/configuration_files.md @@ -551,7 +551,7 @@ This task will fetch current pokemon spawns from /raw_data of an PokemonGo-Map i * `prioritize_vips` - Will prioritize vips in distance and priority mode above all normal pokemon if set to true * `min_time` - Minimum time the pokemon has to be available before despawn * `min_ball` - Minimum amount of balls required to run task -* `max_distance` - Maximum distance the pokemon is allowed to be when walking, ignored when sniping +* `max_distance` - Maximum distance the pokemon is allowed to be when sniping. (km) * `snipe`: - `True` - Will teleport to target pokemon, encounter it, teleport back then catch it - `False` - Will walk normally to the pokemon @@ -573,6 +573,7 @@ This task will fetch current pokemon spawns from /raw_data of an PokemonGo-Map i "type": "MoveToMapPokemon", "config": { "address": "http://localhost:5000", + "//NOTE: Change the max_distance to adjust the max sniping range (km)": {}, "max_distance": 500, "min_time": 60, "min_ball": 50, From a01f4dd02ba4008d04486de338bd315196a73d37 Mon Sep 17 00:00:00 2001 From: DBa2016 Date: Sun, 28 Aug 2016 18:18:16 +0200 Subject: [PATCH 11/38] - debug improvements for MoveToMap (#4860) - fix for Telegram to accept "@username" as "master", too, along with numeric IDs --- pokemongo_bot/__init__.py | 8 ++++++ .../cell_workers/move_to_map_pokemon.py | 25 ++++++++++++++----- pokemongo_bot/cell_workers/telegram_task.py | 17 ++++++++++++- .../event_handlers/telegram_handler.py | 17 ++++++++++--- 4 files changed, 56 insertions(+), 11 deletions(-) diff --git a/pokemongo_bot/__init__.py b/pokemongo_bot/__init__.py index 6891da550e..5d9a945628 100644 --- a/pokemongo_bot/__init__.py +++ b/pokemongo_bot/__init__.py @@ -183,6 +183,8 @@ 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 self.event_manager.register_event( @@ -592,6 +594,12 @@ def _register_events(self): 'moving_to_pokemon_throught_fort', parameters=('fort_name', 'distance','poke_name','poke_dist') ) + self.event_manager.register_event( + 'move_to_map_pokemon', + parameters=('message') + ) + + # cached recent_forts self.event_manager.register_event('loaded_cached_forts') diff --git a/pokemongo_bot/cell_workers/move_to_map_pokemon.py b/pokemongo_bot/cell_workers/move_to_map_pokemon.py index a18a77545d..1003f781e0 100644 --- a/pokemongo_bot/cell_workers/move_to_map_pokemon.py +++ b/pokemongo_bot/cell_workers/move_to_map_pokemon.py @@ -3,6 +3,11 @@ Moves a trainer to a Pokemon. Events: + move_to_map_pokemon + When a generic message is logged + Returns: + message: Log message. + move_to_map_pokemon_fail When the worker fails. Returns: @@ -83,7 +88,7 @@ SNIPE_MAX_IN_CHAIN = 2 # Don't call sniper every time in workers -SNIPE_SKIP_IN_ROUND = 30 +SNIPE_SKIP_IN_ROUND = 5 DEBUG_ON = False @@ -130,7 +135,13 @@ def get_pokemon_from_social(self): pokemon['is_vip'] = pokemon['name'] in self.bot.config.vips if pokemon['name'] not in self.config['catch']: + if DEBUG_ON: + self._emit_failure("Not catching {}".format(pokemon['name'])) continue + else: + if DEBUG_ON: + self._emit_log("Catching {}".format(pokemon['name'])) + if self.was_caught(pokemon): continue @@ -144,7 +155,7 @@ def get_pokemon_from_social(self): pokemon['longitude'], ) - if pokemon['dist'] > self.config['max_distance'] or not self.config['snipe']: + if pokemon['dist'] > self.config['max_distance'] and not self.config['snipe']: continue # pokemon not reachable with mean walking speed (by config) @@ -300,7 +311,7 @@ def work(self): if (pokeballs_quantity + superballs_quantity + ultraballs_quantity) < self.min_ball: if DEBUG_ON: - print 'no enough balls' + self._emit_log("Not enough balls to start sniping (have {}, {} needed)".format(pokeballs_quantity + superballs_quantity + ultraballs_quantity, self.min_ball)) return WorkerResult.SUCCESS self.dump_caught_pokemon() @@ -308,6 +319,8 @@ def work(self): if self.config['snipe']: self.by_pass_times = self.by_pass_times + 1 if self.by_pass_times < SNIPE_SKIP_IN_ROUND: + if DEBUG_ON: + self._emit_log("Skipping pass {}".format(self.by_pass_times)) return WorkerResult.SUCCESS self.by_pass_times = 0 pokemon_list = self.get_pokemon_from_social() @@ -323,12 +336,12 @@ def work(self): if len(pokemon_list) < 1: if DEBUG_ON: - print 'No enough pokemon in list to snip' + self._emit_log("No pokemons in list to snipe") return WorkerResult.SUCCESS pokemon = pokemon_list[0] if DEBUG_ON: - print 'How many pokemon in list: {}'.format(len(pokemon_list)) + self._emit_log('How many pokemon in list: {}'.format(len(pokemon_list))) if self.config['snipe']: if self.snipe_high_prio_only: count = 0 @@ -342,7 +355,7 @@ def work(self): time.sleep(SNIPE_SLEEP_SEC*5) else: if DEBUG_ON: - print 'this pokemon is not good enough to snip {}'.format(pokemon) + self._emit_log('this pokemon is not good enough to snipe {}'.format(pokemon)) return WorkerResult.SUCCESS else: return self.snipe(pokemon) diff --git a/pokemongo_bot/cell_workers/telegram_task.py b/pokemongo_bot/cell_workers/telegram_task.py index 3478f30f37..6591b7183d 100644 --- a/pokemongo_bot/cell_workers/telegram_task.py +++ b/pokemongo_bot/cell_workers/telegram_task.py @@ -6,6 +6,10 @@ from pokemongo_bot.base_task import BaseTask from pokemongo_bot.base_dir import _base_dir from pokemongo_bot.event_handlers import TelegramHandler + +from pprint import pprint +import re + class TelegramTask(BaseTask): SUPPORTED_TASK_API_VERSION = 1 update_id = None @@ -37,8 +41,19 @@ def work(self): 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)) - if self.config.get('master',None) and self.config.get('master',None)<>update.message.from_user.id: + 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', + formatted="Master wrong: expecting {}, got {}({})".format(self.config.get('master',None), update.message.from_user.username, update.message.from_user.id)) continue + else: + if not re.match(r'^[0-9]+$', "{}".format(self.config['master'])): # master was not numeric... + self.config['master'] = update.message.chat_id + idx = (i for i,v in enumerate(self.bot.event_manager._handlers) if type(v) is TelegramHandler).next() + self.bot.event_manager._handlers[idx] = TelegramHandler(self.tbot,self.config['master'], self.config.get('alert_catch')) + + + if update.message.text == "/info": stats = self._get_player_stats() if stats: diff --git a/pokemongo_bot/event_handlers/telegram_handler.py b/pokemongo_bot/event_handlers/telegram_handler.py index 826e2214f5..b32a083225 100644 --- a/pokemongo_bot/event_handlers/telegram_handler.py +++ b/pokemongo_bot/event_handlers/telegram_handler.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from pokemongo_bot.event_manager import EventHandler import thread +import re DEBUG_ON = False @@ -9,15 +10,23 @@ def __init__(self, tbot,master,pokemons): self.tbot = tbot self.master=master self.pokemons=pokemons + self.whoami="TelegramHandler" def handle_event(self, event, sender, level, formatted_msg, data): if self.master: + if not re.match(r'^[0-9]+$', str(self.master)): + return + master = self.master + if event == 'level_up': - self.tbot.sendMessage(chat_id=self.master, parse_mode='Markdown', text="level up ({})".format(data["current_level"])) + msg = "level up ({})".format(data["current_level"]) elif event == 'pokemon_caught': if data["pokemon"] in self.pokemons or self.pokemons[0]=="all": - self.tbot.sendMessage(chat_id=self.master, parse_mode='Markdown', - text="Caught {} CP: {}, IV: {}".format(data["pokemon"],data["cp"],data["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 18d7589edd5c440e0c24f051ff8fcfcf7b7e2531 Mon Sep 17 00:00:00 2001 From: Jasperrr91 Date: Sun, 28 Aug 2016 19:04:47 +0200 Subject: [PATCH 12/38] Fixes catch rates. (#4863) --- pokemongo_bot/cell_workers/pokemon_catch_worker.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pokemongo_bot/cell_workers/pokemon_catch_worker.py b/pokemongo_bot/cell_workers/pokemon_catch_worker.py index d6300ff166..73aa62dee8 100644 --- a/pokemongo_bot/cell_workers/pokemon_catch_worker.py +++ b/pokemongo_bot/cell_workers/pokemon_catch_worker.py @@ -381,6 +381,7 @@ def _do_catch(self, pokemon, encounter_id, catch_rate_by_ball, is_vip=False): min_ultraball_to_keep = self.min_ultraball_to_keep used_berry = False + original_catch_rate_by_ball = catch_rate_by_ball while True: # find lowest available ball @@ -492,6 +493,7 @@ def _do_catch(self, pokemon, encounter_id, catch_rate_by_ball, is_vip=False): data={'pokemon': pokemon.name} ) used_berry = False + catch_rate_by_ball = original_catch_rate_by_ball # sleep according to flee_count and flee_duration config settings # randomly chooses a number of times to 'show' wobble animation between 1 and flee_count From e43f8f98985de8d1bc254f0c04b2b4068101c879 Mon Sep 17 00:00:00 2001 From: DBa2016 Date: Mon, 29 Aug 2016 00:34:29 +0200 Subject: [PATCH 13/38] 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 dc31c8d19052d6fdf70f206632fb60b10d30f634 Mon Sep 17 00:00:00 2001 From: Stuart Travers Date: Mon, 29 Aug 2016 19:52:45 +1000 Subject: [PATCH 14/38] Add exceptions to json file read/writes (#4877) * Fix bot crash at start on permaban * Expanded logging options Added "logging" section to config, with options "color", "show_datetime", "show_process_name" and "show_log_level" * Added warning about deprecated logging_color arg * Display log message moved No point trying to use the logger before it's been initialised. Moved to init_config. * Remove milliseconds from datetime Because really, do we need that? * 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. * Documented new log options * Modified conditions again Removed unnecessary second check for config values and slightly modified parentheses as per suggestion from @mjmadsen * Add exception handling to json file read/write ops * Removed API call in update live stats Instead of making a new api call, utilise stats already contained in metrics. --- 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 | 39 ++++++++++++------- pokemongo_bot/inventory.py | 29 +++++++++++--- pokemongo_bot/metrics.py | 4 ++ 6 files changed, 85 insertions(+), 52 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..743e3b9f19 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. @@ -420,25 +425,31 @@ 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) - 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 = [] 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 3ebabfb13fe75d54556d595809b61ca4627396ae Mon Sep 17 00:00:00 2001 From: DeXtroTip Date: Mon, 29 Aug 2016 14:39:52 +0100 Subject: [PATCH 15/38] 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 de572e24264edfa6003d4a673227a26d47b2fd4c Mon Sep 17 00:00:00 2001 From: Ingwar Wirjawan Date: Mon, 29 Aug 2016 22:10:07 +0700 Subject: [PATCH 16/38] 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 9c6cec4236c5b9b331f08ad07396d540e6699d18 Mon Sep 17 00:00:00 2001 From: Ingwar Wirjawan Date: Mon, 29 Aug 2016 22:18:33 +0700 Subject: [PATCH 17/38] 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 b81aaebc67d9d2f0dcd0c0eedc5822e1806f3615 Mon Sep 17 00:00:00 2001 From: walkerlee Date: Mon, 29 Aug 2016 23:20:01 +0800 Subject: [PATCH 18/38] 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 431a62874934438db9cf8229e0142323b00e473e Mon Sep 17 00:00:00 2001 From: Nikolay Spiridonov Date: Mon, 29 Aug 2016 21:24:40 +0400 Subject: [PATCH 19/38] 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 5eb9fb2447accd252b1cb95433598116425583d5 Mon Sep 17 00:00:00 2001 From: Matt J Madsen Date: Mon, 29 Aug 2016 14:15:48 -0500 Subject: [PATCH 20/38] 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 a0812c15357ade4e35c2fc0fcdfeaea65a92bb69 Mon Sep 17 00:00:00 2001 From: Alexander Skovpen Date: Tue, 30 Aug 2016 06:22:34 +0400 Subject: [PATCH 21/38] 2000 pokestop in 24h limit (#4906) * 2000 pokestop in 24h limit * 2000 pokestop in 24h limit * add config variable * config update --- configs/config.json.example | 3 ++- pokemongo_bot/cell_workers/spin_fort.py | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/configs/config.json.example b/configs/config.json.example index 7200844586..27257803fd 100644 --- a/configs/config.json.example +++ b/configs/config.json.example @@ -203,7 +203,8 @@ "config": { "enabled": true, "spin_wait_min": 3, - "spin_wait_max": 5 + "spin_wait_max": 5, + "daily_spin_limit": 1900 } }, { "type": "UpdateWebInventory", diff --git a/pokemongo_bot/cell_workers/spin_fort.py b/pokemongo_bot/cell_workers/spin_fort.py index 505152d2df..c4a73aec4d 100644 --- a/pokemongo_bot/cell_workers/spin_fort.py +++ b/pokemongo_bot/cell_workers/spin_fort.py @@ -3,6 +3,7 @@ import json import os +import sys import time from pgoapi.utilities import f2i @@ -92,6 +93,9 @@ def work(self): c = conn.cursor() c.execute("SELECT COUNT(name) FROM sqlite_master WHERE type='table' AND name='pokestop_log'") result = c.fetchone() + c.execute("SELECT DISTINCT COUNT(pokestop) FROM pokestop_log WHERE dated >= datetime('now','-1 day')") + if c.fetchone()[0]>=self.config.get('daily_spin_limit',2000): + sys.exit(str(self.config.get('daily_spin_limit',2000))+" Pokestop spin in 24 hours") while True: if result[0] == 1: conn.execute('''INSERT INTO pokestop_log (pokestop, exp, items) VALUES (?, ?, ?)''', (fort_name, str(experience_awarded), str(items_awarded))) From 48e507b81a765bc19f549cb89af7c150546cd9ca Mon Sep 17 00:00:00 2001 From: Alex Yao Date: Mon, 29 Aug 2016 19:24:32 -0700 Subject: [PATCH 22/38] Update readme.md + Improve FollowPath & SleepSchedule messages (#4911) * Use logger for follow path loiter message * Update readme.md * Improve sleep message * Allow follow_path to use config's distance unit * Allow follow_path to use config's distance unit --- README.md | 22 +++++++++++----------- pokemongo_bot/__init__.py | 5 ++++- pokemongo_bot/cell_workers/follow_path.py | 12 +++++++----- pokemongo_bot/cell_workers/utils.py | 11 +++++++---- pokemongo_bot/sleep_schedule.py | 5 +++-- 5 files changed, 32 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index a64e5774ea..4e62f39504 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,26 @@ # PokemonGo-Bot -PokemonGo bot is a project created by the [PokemonGoF](https://github.com/PokemonGoF) team. +[PokemonGo-Bot](https://github.com/PokemonGoF/PokemonGo-Bot) is a project created by the [PokemonGoF](https://github.com/PokemonGoF) team. ## Table of Contents - [Installation](https://github.com/PokemonGoF/PokemonGo-Bot/blob/dev/docs/installation.md) - [Documentation](https://github.com/PokemonGoF/PokemonGo-Bot/blob/dev/docs/) - [Support](#support) - - [help](#configuration-issueshelp) - - [bugs](#bugs--issues) - - [Feature request](#feature-requests) + - [Help](#configuration-issueshelp) + - [Bugs](#bugs--issues) + - [Feature Requests](#feature-requests) - [Pull Requests](#pull-requests) - [Features](#features) - [Credits](#credits) -The project is currently setup in two main branches. -- `dev` also known as `beta` - This is where the latest features are, but you may also experience some issues with stability/crashes -- `master` also known as `stable` - The bot 'should' be stable on this branch, and is generally well tested +The project is currently setup in two main branches: +- `dev` also known as `beta` - This is where the latest features are, but you may also experience some issues with stability/crashes. +- `master` also known as `stable` - The bot 'should' be stable on this branch, and is generally well tested. ## Support ### Configuration issues/help If you need any help please don't create an issue as we have a great community on Slack. You can count on the community in [#help](https://pokemongo-bot.slack.com/messages/help/) channel. - - [Click here to signup (first time only)](https://pokemongo-bot.herokuapp.com) - - [Join if you're already a member](https://pokemongo-bot.slack.com/messages/general/). + - [Click here to signup (first time only)](https://pokemongo-bot.herokuapp.com) + - [Join here if you're already a member](https://pokemongo-bot.slack.com/messages/general/) ###[Bugs / Issues](https://github.com/PokemonGoF/PokemonGo-Bot/issues?q=is%3Aissue+sort%3Aupdated-desc) If you discover a bug in the bot, please [search our issue tracker](https://github.com/PokemonGoF/PokemonGo-Bot/issues?q=is%3Aissue+sort%3Aupdated-desc) first. If it hasn't been reported, please [create a new issue](https://github.com/PokemonGoF/PokemonGo-Bot/issues/new) and ensure you follow the template guide so that our team can assist you as quickly as possible. @@ -51,10 +51,10 @@ If you'd like to make your own changes, make sure you follow the pull request te - [ ] Use candy ## Gym Battles -This bot takes a strong stance against automating gym battles. Botting gyms will have a negative effect on most players and thus the game as a whole. We will thus never accept contributions or changes containing code specific for gym battles. +[PokemonGo-Bot](https://github.com/PokemonGoF/PokemonGo-Bot) takes a strong stance against automating gym battles. Botting gyms will have a negative effect on most players and thus the game as a whole. We will thus never accept contributions or changes containing code specific for gym battles. ## Analytics -This bot is very popular and has a vibrant community. Because of that, it has become very difficult for us to know how the bot is used and what errors people hit. By capturing small amounts of data, we can prioritize our work better such as fixing errors that happen to a large percentage of our user base, not just a vocal minority. +[PokemonGo-Bot](https://github.com/PokemonGoF/PokemonGo-Bot) is very popular and has a vibrant community. Because of that, it has become very difficult for us to know how the bot is used and what errors people hit. By capturing small amounts of data, we can prioritize our work better such as fixing errors that happen to a large percentage of our user base, not just a vocal minority. Our goal is to help inform our decisions by capturing data that helps us get aggregate usage and error reports, not personal information. To view the code that handles analytics in our master branch, you can use this [search link](https://github.com/PokemonGoF/PokemonGo-Bot/search?utf8=%E2%9C%93&q=BotEvent). diff --git a/pokemongo_bot/__init__.py b/pokemongo_bot/__init__.py index 8a332fcd6d..971f1ea0b6 100644 --- a/pokemongo_bot/__init__.py +++ b/pokemongo_bot/__init__.py @@ -235,7 +235,10 @@ def _register_events(self): # sleep stuff self.event_manager.register_event( 'next_sleep', - parameters=('time',) + parameters=( + 'time', + 'duration' + ) ) self.event_manager.register_event( 'bot_sleep', diff --git a/pokemongo_bot/cell_workers/follow_path.py b/pokemongo_bot/cell_workers/follow_path.py index 6b2ed51cbe..34cdb46108 100644 --- a/pokemongo_bot/cell_workers/follow_path.py +++ b/pokemongo_bot/cell_workers/follow_path.py @@ -11,7 +11,7 @@ from pokemongo_bot.worker_result import WorkerResult from pgoapi.utilities import f2i from random import uniform -from utils import getSeconds +from utils import getSeconds, format_dist from datetime import datetime as dt, timedelta STATUS_MOVING = 0 @@ -26,6 +26,8 @@ def initialize(self): self.points = self.load_path() self.status = STATUS_MOVING self.loiter_end_time = 0 + self.distance_unit = self.bot.config.distance_unit + self.append_unit = False if self.path_start_mode == 'closest': self.ptr = self.find_closest_point_idx(self.points) @@ -181,14 +183,14 @@ def work(self): data={ 'last_position': (last_lat, last_lng, last_alt), 'current_position': (lat, lng, alt), - 'distance': dist, - 'distance_unit': 'm' + 'distance': format_dist(dist,self.distance_unit,self.append_unit), + 'distance_unit': self.distance_unit } ) if dist <= 1 or (self.bot.config.walk_min > 0 and is_at_destination) or (self.status == STATUS_LOITERING and time.time() >= self.loiter_end_time): - if "loiter" in point and self.status != STATUS_LOITERING: - print("Loitering {} seconds".format(point["loiter"])) + if "loiter" in point and self.status != STATUS_LOITERING: + self.logger.info("Loitering for {} seconds...".format(point["loiter"])) self.status = STATUS_LOITERING self.loiter_end_time = time.time() + point["loiter"] return WorkerResult.SUCCESS diff --git a/pokemongo_bot/cell_workers/utils.py b/pokemongo_bot/cell_workers/utils.py index 452176e687..a5dc6b8888 100644 --- a/pokemongo_bot/cell_workers/utils.py +++ b/pokemongo_bot/cell_workers/utils.py @@ -111,14 +111,17 @@ def convert(distance, from_unit, to_unit): # Converts units return distance * conversions[from_unit][to_unit] -def dist_to_str(distance, unit): - return '{:.2f}{}'.format(distance, unit) +def dist_to_str(distance, unit, append_unit = True): + if append_unit: + return '{:.2f}{}'.format(distance, unit) + else: + return '{:.2f}'.format(distance) -def format_dist(distance, unit): +def format_dist(distance, unit, append_unit = True): # Assumes that distance is in meters and converts it to the given unit, then a formatted string is returned # Ex: format_dist(1500, 'km') returns the string "1.5km" - return dist_to_str(convert(distance, 'm', unit), unit) + return dist_to_str(convert(distance, 'm', unit), unit, append_unit) def getSeconds(strTime): diff --git a/pokemongo_bot/sleep_schedule.py b/pokemongo_bot/sleep_schedule.py index bfa4e044fb..3ce10e24ef 100644 --- a/pokemongo_bot/sleep_schedule.py +++ b/pokemongo_bot/sleep_schedule.py @@ -101,9 +101,10 @@ def _schedule_next_sleep(self): self.bot.event_manager.emit( 'next_sleep', sender=self, - formatted="Next sleep at {time}", + formatted="Next sleep at {time}, for a duration of {duration}", data={ - 'time': str(self._next_sleep) + 'time': str(self._next_sleep.strftime("%H:%M:%S")), + 'duration': str(timedelta(seconds=self._next_duration)) } ) From 734a0a3d5abe8399411ba5e3a55a44813f211feb Mon Sep 17 00:00:00 2001 From: Stuart Travers Date: Tue, 30 Aug 2016 12:33:44 +1000 Subject: [PATCH 23/38] Reduce API calls (#4916) * Fix bot crash at start on permaban * Expanded logging options Added "logging" section to config, with options "color", "show_datetime", "show_process_name" and "show_log_level" * Added warning about deprecated logging_color arg * Display log message moved No point trying to use the logger before it's been initialised. Moved to init_config. * Remove milliseconds from datetime Because really, do we need that? * 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. * Documented new log options * Modified conditions again Removed unnecessary second check for config values and slightly modified parentheses as per suggestion from @mjmadsen * Add exception handling to json file read/write ops * Removed API call in update live stats Instead of making a new api call, utilise stats already contained in metrics. * Update player data in web from metrics Uses existing metrics instead of waiting on liveupdate * Implemented more granularity in the "alert_catch" parameter for Telegram alerts. * 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 * 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 * Call level_up_rewards on exp changes/Some pep-8 (#4896) * Call level_up_rewards on exp changes. * Cleanup * add some sanitycheck (#4891) * 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 * 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 * 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. * 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. * Fixed conflict * Import inventory added to metrics Allows metrics to use the cached inventory to retrieve player stats instead of making another api call * Removed api call from incubate_eggs Cached inventory should be accurate enough for this --- pokemongo_bot/__init__.py | 2 + pokemongo_bot/cell_workers/incubate_eggs.py | 3 +- .../cell_workers/update_live_stats.py | 47 ++----------------- .../cell_workers/update_web_inventory.py | 5 +- pokemongo_bot/inventory.py | 32 +++++-------- pokemongo_bot/metrics.py | 9 ++-- 6 files changed, 29 insertions(+), 69 deletions(-) diff --git a/pokemongo_bot/__init__.py b/pokemongo_bot/__init__.py index 971f1ea0b6..de996f3df3 100644 --- a/pokemongo_bot/__init__.py +++ b/pokemongo_bot/__init__.py @@ -1247,6 +1247,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/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", {}) diff --git a/pokemongo_bot/cell_workers/update_live_stats.py b/pokemongo_bot/cell_workers/update_live_stats.py index 743e3b9f19..e6689e3e4c 100644 --- a/pokemongo_bot/cell_workers/update_live_stats.py +++ b/pokemongo_bot/cell_workers/update_live_stats.py @@ -10,6 +10,7 @@ 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 @@ -163,14 +164,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 +208,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() @@ -332,7 +331,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 @@ -417,39 +415,4 @@ 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) + \ No newline at end of file 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/inventory.py b/pokemongo_bot/inventory.py index 2a858b6c11..d4a2efc5f3 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'] @@ -1170,7 +1174,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 +1206,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 +1219,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 +1334,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..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): @@ -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 d7cf5558e56c9ca9611cec1f228365541f25f2bd Mon Sep 17 00:00:00 2001 From: Alex Yao Date: Mon, 29 Aug 2016 19:34:40 -0700 Subject: [PATCH 24/38] Swap auth and config position (#4909) --- run.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/run.sh b/run.sh index 5f8d199bee..dac6c19484 100755 --- a/run.sh +++ b/run.sh @@ -3,14 +3,14 @@ pokebotpath=$(cd "$(dirname "$0")"; pwd) auth="" config="" if [ ! -z $1 ]; then - auth=$1 + config=$1 else - auth="./configs/auth.json" + config="./configs/config.json" fi if [ ! -z $2 ]; then - config=$2 + auth=$2 else - config="./configs/config.json" + auth="./configs/auth.json" fi cd $pokebotpath source bin/activate From 94c4343b21cf46941617fd2b049b24bbab56d9d7 Mon Sep 17 00:00:00 2001 From: Alexander Skovpen Date: Tue, 30 Aug 2016 12:47:52 +0400 Subject: [PATCH 25/38] add telegram check messages interval (#4919) * add telegram check messages interval * config changed * fix config * telegram doc update --- configs/config.json.example | 11 ++++++----- docs/configuration_files.md | 13 ++++++++----- pokemongo_bot/cell_workers/telegram_task.py | 11 +++++++++-- 3 files changed, 23 insertions(+), 12 deletions(-) diff --git a/configs/config.json.example b/configs/config.json.example index 27257803fd..9cde3ee824 100644 --- a/configs/config.json.example +++ b/configs/config.json.example @@ -12,12 +12,13 @@ "config": { "enabled": false, "master": null, + "min_interval": 120, "// 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} - } + "// new syntax:": {}, + "alert_catch": { + "all": {"operator": "and", "cp": 1300, "iv": 0.95}, + "Snorlax": {"operator": "or", "cp": 900, "iv": 0.9} + } } }, { diff --git a/docs/configuration_files.md b/docs/configuration_files.md index ed615b0045..8f4abb9625 100644 --- a/docs/configuration_files.md +++ b/docs/configuration_files.md @@ -989,8 +989,9 @@ Bot answer on command '/info' self stats. ### Options * `telegram_token` : bot token (getting [there](https://core.telegram.org/bots#6-botfather) - one token per bot) -* `master` : id (without quotes) or username (in quotes, first character @) of bot owner, who will gett announces. -* `alert_catch` : array of pokemons, which will be announced on catch. if first array item `all` - announce all pokemons. +* `master` : id (without quotes) of bot owner, who will gett announces. +* `alert_catch` : dict of rules pokemons catch. +* `min_interval`: min interval check messages from telegram. ### Sample configuration [[back to top](#table-of-contents)] @@ -1000,9 +1001,11 @@ Bot answer on command '/info' self stats. "config": { "enabled": true, "master": 12345678, - "//master": "@username", - "alert_catch": ["Lapras","Dragonite"], - "//alert_catch": ["all"] + "min_interval": 120, + "alert_catch": { + "all": {"operator": "and", "cp": 1300, "iv": 0.95}, + "Snorlax": {"operator": "or", "cp": 900, "iv": 0.9} + } } } ``` diff --git a/pokemongo_bot/cell_workers/telegram_task.py b/pokemongo_bot/cell_workers/telegram_task.py index 5c2b60787e..f9c421aff5 100644 --- a/pokemongo_bot/cell_workers/telegram_task.py +++ b/pokemongo_bot/cell_workers/telegram_task.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +import datetime import telegram import os import logging @@ -17,7 +18,9 @@ class TelegramTask(BaseTask): SUPPORTED_TASK_API_VERSION = 1 update_id = None tbot = None - + min_interval=None + next_job=None + def initialize(self): if not self.enabled: return @@ -35,10 +38,14 @@ def initialize(self): self.update_id = self.tbot.getUpdates()[0].update_id except IndexError: self.update_id = None - + self.min_interval=self.config.get('min_interval',120) + self.next_job=datetime.now() + timedelta(seconds=self.min_interval) def work(self): if not self.enabled: return + if datetime.now() Date: Tue, 30 Aug 2016 16:49:02 +0800 Subject: [PATCH 26/38] Add documentation (#4921) See documentation for full list of new features --- configs/config.json.optimizer.example | 357 ++------------ docs/pokemon_optimizer.md | 452 ++++++++++++++++++ pokemongo_bot/base_task.py | 2 +- .../cell_workers/pokemon_optimizer.py | 398 +++++++++------ 4 files changed, 754 insertions(+), 455 deletions(-) create mode 100644 docs/pokemon_optimizer.md diff --git a/configs/config.json.optimizer.example b/configs/config.json.optimizer.example index 0f1da751d3..3005ebbd80 100644 --- a/configs/config.json.optimizer.example +++ b/configs/config.json.optimizer.example @@ -1,312 +1,49 @@ { - "websocket_server": false, - "heartbeat_threshold": 10, - "enable_social": true, - "live_config_update": { - "enabled": false, - "tasks_only": false - }, - "tasks": [ - { - "//NOTE: This task MUST be placed on the top of task list": {}, - "type": "RandomAlivePause", - "config": { - "enabled": false, - "min_duration": "00:00:10", - "max_duration": "00:10:00", - "min_interval": "00:05:00", - "max_interval": "01:30:00" - } - }, - { - "type": "HandleSoftBan" - }, - { - "type": "CompleteTutorial", - "config": { - "enabled": false, - "// set a name": "", - "nickname": "" - } - }, - { - "type": "CollectLevelUpReward", - "config": { - "collect_reward": true, - "level_limit": -1 - } - }, - { - "type": "IncubateEggs", - "config": { - "enabled": true, - "infinite_longer_eggs_first": false, - "breakable_longer_eggs_first": true, - "min_interval": 120 - } - }, - { - "type": "UpdateLiveStats", - "config": { - "enabled": false, - "min_interval": 10, - "stats": ["username", "uptime", "stardust_earned", "xp_earned", "xp_per_hour", "stops_visited"], - "terminal_log": true, - "terminal_title": true - } - }, - { - "type": "UpdateLiveInventory", - "config": { - "enabled": false, - "min_interval": 120, - "show_all_multiple_lines": false, - "items": ["pokemon_bag", "space_info", "pokeballs", "greatballs", "ultraballs", "razzberries", "luckyegg"] - } - }, - { - "type": "ShowBestPokemon", - "config": { - "enabled": true, - "min_interval": 60, - "amount": 5, - "order_by": "cp", - "info_to_show": ["cp", "ivcp", "dps", "hp"] - } - }, - { - "type": "PokemonOptimizer", - "config": { - "enabled": true, - "// the 'transfer' parameter activate or deactivate the transfer of pokemons": {}, - "// at false, no pokemon is going to be transfered, ever": {}, - "// at false, you will still get the log information of what the optimizer": {}, - "// would have transfered if the parameter was true": {}, - "transfer": true, - "// 'transfer_wait_min' and 'transfer_wait_max' are the minimum and maximum": {}, - "// time to wait when transferring a pokemon": {}, - "transfer_wait_min": 3, - "transfer_wait_max": 5, - "// the 'evolve' parameter activate or deactivate the evolution of pokemons": {}, - "// at false, no pokemon is going to be evolved, ever": {}, - "// at false, you will still get the log information of what the": {}, - "// optimizer would have evolved if the parameter was true": {}, - "evolve": true, - "// Time in seconds to wait between two evolve": {}, - "evolve_time": 20, - "// the 'evolve_for_xp' parameter let you choose if you want the optimizer": {}, - "// to use your candies to evolve low quality pokemons in order to maximize your xp": {}, - "// at false, the optimizer will still use candies to evolve your best Pokemons": {}, - "evolve_for_xp": true, - "// the 'evolve_only_with_lucky_egg' parameter let you choose if you want the optimizer": {}, - "// to only Evolve Pokemons when a lucky egg is available": {}, - "evolve_only_with_lucky_egg": false, - "// the 'evolve_count_for_lucky_egg' parameter let you define the minimum": {}, - "// number of Pokemons that must evolve before using a lucky egg": {}, - "// If that number is not reached, and evolve_only_with_lucky_egg is true, evolution will be skipped": {}, - "// If that number is not reached, and evolve_only_with_lucky_egg is false,": {}, - "// evolution will be performed without using a lucky egg": {}, - "evolve_count_for_lucky_egg": 92, - "// the 'may_use_lucky_egg' parameter let you choose if you want the optimizer": {}, - "// to use a lucky egg right before evolving Pokemons. At false; the optimizer": {}, - "// is free to evolve Pokemons even if you do not have any lucky egg.": {}, - "may_use_lucky_egg": true, - "// the 'keep' parameter let you define what pokemons you consider are the 'best'. These Pokemons": {}, - "// will be keep and evolved. Note that Pokemons are evaluated inside their whole family": {}, - "// Multiple way of ranking can be defined. Following configuration let you keep the best iv,": {}, - "// the best ncp and the best cp": {}, - "keep": [ - { - "// Following setting let you keep the best iv of the family": {}, - "// the 'top' parameter allow you to define how many Pokemons you want to keep": {}, - "// at the top of your ranking. If several Pokemons get the same score, they are": {}, - "// considered equal. Thus, top=1 might result in keeping more than 1 Pokemon.": {}, - "top": 1, - "// the 'evolve' parameter let you choose if you want to evolve the Pokemons you keep": {}, - "evolve": true, - "// the 'sort' parameter define how you want to rank your pokemons": {}, - "// Critera are sorted fro, the most important to the least important.": {}, - "// Available criteria are:": {}, - "// 'iv' = individual value": {}, - "// 'ivcp' = iv weigted so that for equal iv, attack > defense > stamina": {}, - "// 'cp' = combat power (can be increased with candies)": {}, - "// 'cp_exact' = combat power (not rounded)": {}, - "// 'ncp' (normalized cp) or 'cp_percent' = ratio cp / max_cp": {}, - "// 'iv_attack' = attach component of iv": {}, - "// 'iv_defense' = defense component of iv": {}, - "// 'iv_stamina' = stamina component of iv": {}, - "// 'dps' = raw dps based on the moves of the pokemon": {}, - "// 'dps_attack' = average dps when attacking": {}, - "// 'attack_perfection' = ratio dps_attack / best_dps_attack. Return same order as 'dps_attack'": {}, - "// 'dps_defense' = average dps when defending": {}, - "// 'defense_perfection' = ratio dps_defense / best_dps_defense. Return same order as 'dps_defense'": {}, - "// 'hp' = current health points": {}, - "// 'hp_max' = max health points": {}, - "// Note that the more criteria you add to this list, the less likely Pokemons": {}, - "// will be equals": {}, - "sort": ["iv"] - }, - { - "// Following setting let you keep keep the best normalized cp of the family": {}, - "// That is the Pokemon with higher CP once fully evolved": {}, - "top": 1, - "evolve": true, - "sort": ["ncp"] - }, - { - "// Following setting let you keep keep the best cp of the family.": {}, - "// But will not evolve it further (in favor of the best ncp)": {}, - "// It will only applies to the family of 'Dragonite', 'Arcanine' and 'Muk'": {}, - "// Other families are not following this setting": {}, - "names": ["Dragonite", "Arcanine", "Muk"], - "top": 1, - "evolve": false, - "sort": ["cp"] - } - ] - } - }, - { - "type": "RecycleItems", - "config": { - "enabled": true, - "min_empty_space": 15, - "max_balls_keep": 150, - "max_potions_keep": 50, - "max_berries_keep": 70, - "max_revives_keep": 70, - "item_filter": { - "Pokeball": { "keep" : 100 }, - "Potion": { "keep" : 10 }, - "Super Potion": { "keep" : 20 }, - "Hyper Potion": { "keep" : 30 }, - "Revive": { "keep" : 30 }, - "Razz Berry": { "keep" : 100 } - }, - "recycle_wait_min": 3, - "recycle_wait_max": 5, - "recycle_force": true, - "recycle_force_min": "00:01:00", - "recycle_force_max": "00:05:00" - } - }, - { - "type": "CatchPokemon", - "config": { - "enabled": true, - "catch_visible_pokemon": true, - "catch_lured_pokemon": true, - "min_ultraball_to_keep": 5, - "berry_threshold": 0.35, - "vip_berry_threshold": 0.9, - "treat_unseen_as_vip": true, - "daily_catch_limit": 800, - "catch_throw_parameters": { - "excellent_rate": 0.1, - "great_rate": 0.5, - "nice_rate": 0.3, - "normal_rate": 0.1, - "spin_success_rate" : 0.6 - }, - "catch_simulation": { - "flee_count": 3, - "flee_duration": 2, - "catch_wait_min": 3, - "catch_wait_max": 6, - "berry_wait_min": 3, - "berry_wait_max": 5, - "changeball_wait_min": 3, - "changeball_wait_max": 5, - "newtodex_wait_min": 20, - "newtodex_wait_max": 30 - } - } - }, - { - "type": "SpinFort", - "config": { - "enabled": true, - "spin_wait_min": 3, - "spin_wait_max": 5 - } - }, - { "type": "UpdateWebInventory", - "config": { - "enabled": true - } - }, - { - "type": "MoveToFort", - "config": { - "enabled": true, - "lure_attraction": true, - "lure_max_distance": 2000, - "log_interval": 5 - } - }, - { - "type": "FollowSpiral", - "config": { - "enabled": true, - "diameter": 4, - "step_size": 70 - } - } - ], - "map_object_cache_time": 5, - "forts": { - "avoid_circles": true, - "max_circle_size": 50, - "cache_recent_forts": true - }, - "pokemon_bag": { - "// if 'show_at_start' is true, it will log all the pokemons in the bag (not eggs) at bot start": {}, - "show_at_start": true, - "// if 'show_count' is true, it will show the amount of each pokemon (minimum 1)": {}, - "show_count": false, - "// if 'show_candies' is true, it will show the amount of candies for each pokemon": {}, - "show_candies": false, - "// 'pokemon_info' parameter define which info to show for each pokemon": {}, - "// the available options are": {}, - "// ['cp', 'iv_ads', 'iv_pct', 'ivcp', 'ncp', 'level', 'hp', 'moveset', 'dps']": {}, - "pokemon_info": ["cp", "iv_pct"] - }, - "walk_max": 4.16, - "walk_min": 2.16, - "alt_min": 500, - "alt_max": 1000, - "sleep_schedule": [ - { - "time": "12:00", - "duration": "5:30", - "time_random_offset": "00:30", - "duration_random_offset": "00:30", - "wake_up_at_location": "" - }, - { - "time": "17:45", - "duration": "3:00", - "time_random_offset": "01:00", - "duration_random_offset": "00:30", - "wake_up_at_location": "" - } - ], - "debug": false, - "test": false, - "walker_limit_output": false, - "health_record": true, - "location_cache": true, - "distance_unit": "km", - "reconnecting_timeout": 15, - "logging": { - "color": true, - "show_datetime": true, - "show_process_name": true, - "show_log_level": true - }, - "catch": { - "any": { - "always_catch": true - } - } + "tasks": [ + { + "type": "PokemonOptimizer", + "config": { + "enabled": true, + "transfer": true, + "transfer_wait_min": 3, + "transfer_wait_max": 5, + "evolve": true, + "evolve_time": 25, + "evolve_for_xp": true, + "evolve_only_with_lucky_egg": false, + "evolve_count_for_lucky_egg": 80, + "may_use_lucky_egg": true, + "groups": { + "gym": ["Dragonite", "Snorlax"] + }, + "keep": [ + { + "mode": "by_family", + "top": 1, + "sort": [{"iv": 0.9}], + "evolve": true + }, + { + "mode": "by_family", + "top": 1, + "sort": [{"ncp": "0.9"}], + "evolve": true + }, + { + "mode": "by_family", + "top": 1, + "sort": ["cp"], + "evolve": false + }, + { + "mode": "by_pokemon", + "names": ["gym"], + "top": 5, + "sort": ["cp"], + "evolve": false + } + ] + } + } + ] } diff --git a/docs/pokemon_optimizer.md b/docs/pokemon_optimizer.md new file mode 100644 index 0000000000..4109d90f9b --- /dev/null +++ b/docs/pokemon_optimizer.md @@ -0,0 +1,452 @@ +# Pokemon Optimizer +- [About](#about) +- [Configuration](#configuration) + - [Default configuration](#default-configuration) + - [Understand parameters](#understand-parameters) + - [enabled](#enabled) + - [transfer](#transfer) + - [transfer_wait_min](#transfer_wait_min) + - [transfer_wait_max](#transfer_wait_max) + - [evolve](#evolve) + - [evolve_time](#evolve_time) + - [evolve_for_xp](#evolve_for_xp) + - [evolve_only_with_lucky_egg](#evolve_only_with_lucky_egg) + - [evolve_count_for_lucky_egg](#evolve_count_for_lucky_egg) + - [may_use_lucky_egg](#may_use_lucky_egg) + - [groups](#groups) + - [keep](#keep) + - [mode](#keep-mode) + - [names](#keep-names) + - [top](#keep-top) + - [sort](#keep-sort) + - [evolve](#keep-evolve) + - [Examples of configuration](#examples-of-configuration) +- [Eevee case](#eevee-case) + +# About +The Pokemon Optimizer manage transfer and evolution of your Pokemon. +
It can replace or complement the classical Evolve and Transfer tasks. +
It will be triggered when you bag of Pokemon is full and has no effect until it happens. + +[[back to top](#pokemon-optimizer)] + +# Configuration +## Default configuration +``` +{ + "tasks": [ + { + "type": "PokemonOptimizer", + "config": { + "enabled": true, + "transfer": true, + "transfer_wait_min": 3, + "transfer_wait_max": 5, + "evolve": true, + "evolve_time": 25, + "evolve_for_xp": true, + "evolve_only_with_lucky_egg": false, + "evolve_count_for_lucky_egg": 80, + "may_use_lucky_egg": true, + "keep": [ + { + "mode": "by_family", + "top": 1, + "sort": [{"iv": 0.9}], + "evolve": true + }, + { + "mode": "by_family", + "top": 1, + "sort": [{"ncp": "0.9"}], + "evolve": true + }, + { + "mode": "by_family", + "top": 1, + "sort": ["cp"], + "evolve": false + } + ] + } + } + ] +} +``` + +[[back to top](#pokemon-optimizer)] + +## Understand parameters +### enabled +| Parameter | Possible values | Default | +|-----------|-----------------|---------| +| `enabled` | `true`, `false` | `true` | + +Enable or disable the task. + +[[back to top](#pokemon-optimizer)] + +### transfer +| Parameter | Possible values | Default | +|------------|-----------------|---------| +| `transfer` | `true`, `false` | `false` | + +The `transfer` parameter activate or deactivate the transfer of Pokemon. + +At `true`, you allow the Pokemon Optimizer to transfer every Pokemon that are not good enough to be kept according to your own criteria. +
At `false`, and regardless of other parameters, no Pokemon is ever going to be transfered. + +Note that, whatever is the value you choose to give to that parameter, you will still see logs explaining which Pokemon are transfered. +
The purpose of this is to show you what choices are made by the Pokemon Optimizer. +It can help you rectify your configuration or guide you during manual transfer. + +`Exchanged Magikarp [IV 0.4] [CP 69] [481 candies]` + +[[back to top](#pokemon-optimizer)] + +### transfer_wait_min +| Parameter | Possible values | Default | +|---------------------|-----------------|---------| +| `transfer_wait_min` | `[0-N]` | `3` | + +This is the minimum time to wait after transferring a Pokemon. + +[[back to top](#pokemon-optimizer)] + +### transfer_wait_max +| Parameter | Possible values | Default | +|---------------------|-----------------|---------| +| `transfer_wait_max` | `[0-N]` | `5` | + +This is the maximum time to wait after transferring a Pokemon. + +[[back to top](#pokemon-optimizer)] + +### evolve +| Parameter | Possible values | Default | +|-----------|-----------------|---------| +| `evolve` | `true`, `false` | `false` | + +The `evolve` parameter activate or deactivate the evolution of Pokemon. + +At `true`, you allow the Pokemon Optimizer to evolve every Pokemon that are the best according to your own criteria. +
You also allow it to evolve lower quality Pokemon when [`evolve_for_xp`](#evolve_for_xp) parameter is `true`. +
At `false`, and regardless of other parameters, no Pokemon is ever going to be evolved. + +Note that, whatever is the value you choose to give to that parameter, you will still see logs explaining which Pokemon are evolved. +
The purpose of this is to show you what choices are made by the Pokemon Optimizer. +It can help you rectify your configuration or guide you during manual evolution. + +`Evolved Magikarp [IV 0.96] [CP 231] [+1000 xp] [82 candies]` + +[[back to top](#pokemon-optimizer)] + +### evolve_time +| Parameter | Possible values | Default | +|---------------|-----------------|---------| +| `evolve_time` | `[0-N]` | `25` | + +This is the duration of the evolution animation and time to wait after performing an evolution. +
The actual time used is randomized between more or less 10% of the parameter value. + +[[back to top](#pokemon-optimizer)] + +### evolve_for_xp +| Parameter | Possible values | Default | +|-----------------|-----------------|---------| +| `evolve_for_xp` | `true`, `false` | `true` | + +Let you choose if you want the Pokemon Otimizer to use your candies to evolve low quality Pokemon. + +Better quality Pokemon have priority for evolution and the Pokemon Optimizer will never evolve for xp if a better Pokemon is waiting for candies to evolve. +
These low quality Pokemon will only be used if you have plenty of candies left after evolving your best Pokemon. + +The below 2% rule help the Pokemon Optimizer to disregard rare Pokemon and focus on common Pokemon to evolve for xp. + +#### 2% rule +For each family of Pokemon, if, after evolving your best Pokemon, you have enough candies left to evolve 2% of your total bag capacity, the first rank of the family are eligible for xp evolution. +
If you do not have enough candies or Pokemon to evolve these 2%, they will be transfered. + +For example, for a bag total capacity of 250, you must have enough candies and Pokemon to evolve 5 of them for xp evolution. +
This usually means that the rarest Pokemon in your area will never be eligible since you never have 5 low quality of them at the point where your bag is full. + +[[back to top](#pokemon-optimizer)] + +### evolve_only_with_lucky_egg +| Parameter | Possible values | Default | +|------------------------------|-----------------|---------| +| `evolve_only_with_lucky_egg` | `true`, `false` | `false` | + +Force the Pokemon Optimizer to wait that a lucky egg is available to perform evolution. +
It is advised to keep this parameter to `false` since it is quite restrictive. +
You do not always have a lucky egg available and you still need to clean up your bag to make place for new Pokemon. + +At `true`, no evolution will be performed unless we have an available lucky egg to use before. + +[[back to top](#pokemon-optimizer)] + +### evolve_count_for_lucky_egg +| Parameter | Possible values | Default | +|------------------------------|-----------------|---------| +| `evolve_count_for_lucky_egg` | `[0-N]` | `80` | + +If you allow the Pokemon Optimizer to use a lucky egg, this parameter let you define the minimum number of Pokemons that must evolve when using a lucky egg. + +If a lucky egg is available, the Pokemon Optimizer is going to wait that number is reached to perform evolution. +
If you do not have any available lucky egg, the Pokemon Optimizer will ignore this parameter and evolution will be performed without lucky egg. +
It may take long time before reaching that number. + +[[back to top](#pokemon-optimizer)] + +### may_use_lucky_egg +| Parameter | Possible values | Default | +|---------------------|-----------------|---------| +| `may_use_lucky_egg` | `true`, `false` | `true` | + +Define whether you allow the Pokemon Optimizer to use a lucky egg before evolving Pokemon or not. +
At `true`, and if a lucky egg is available, the Pokemon Optimizer will wait for the [`evolve_count_for_lucky_egg`](#evolve_count_for_lucky_egg) Pokemon to use the lucky egg. +
At `false`, or when no luck egg is available, no lucky egg will be used. + +[[back to top](#pokemon-optimizer)] + +### groups +| Parameter | Possible values | Default | +|-----------|-----------------|---------| +| `groups` | (see below) | `{}` | + +You can define `groups` of Pokemon to help you restrict rules to a specific set of Pokemon. +
You can then use these `groups` names in the [`names`](#keep-names) parameter of your rule to refer to list of Pokemon + +`groups` are list of Pokemon names: +``` +"groups": { + "gym": ["Dragonite", "Snorlax"], + "my_love": ["Pikachu"], + "vip": ["Lapras", "Arcanine", "Gyarados", "gym"], + "trash": ["!vip", "!my_love"] +}, +``` + +A same Pokemon name can appear in different `groups`. And `groups` may reference each others. +
Just like [`names`](#keep-names), you can also negate a group by preceding its name by a `!` or `-`. +
Including `groups` and negating others allow you to create group unions and/or intersections. + +[[back to top](#pokemon-optimizer)] + +### keep +| Parameter | Possible values | Default | +|-----------|-----------------|-------------| +| `keep` | (see below) | (see below) | + +This parameter is a list that contains as many element as you want. +
Each element of that list define a rule that select what Pokemon are the best. +
Each of these rules is going to be handled individually and will result in a list of Pokemon to keep. + +The conjunction of all rules define the list of all Pokemon to keep. +Every Pokemon not selected is candidate for transfer. + +``` +[ + {"mode": "by_family", "top": 1, "sort": [{"iv": 0.9}], "evolve": True}, + {"mode": "by_family", "top": 1, "sort": [{"ncp": 0.9}], "evolve": True}, + {"mode": "by_family", "top": 1, "sort": ["cp"], "evolve": False} +] +``` + +[[back to top](#pokemon-optimizer)] + +#### keep mode +| Parameter | Possible values | Default | +|-----------|--------------------------------------------|---------------| +| `mode` | `"by_pokemon"`, `"by_family"`, `"overall"` | `"by_family"` | + +The `mode` define how the Pokemon Optimizer is going to apply the rule. +- `"by_pokemon"` will apply the rule for each Pokemon. +
In that mode, each Pokemon will be compared to other Pokemon of the same name. +
This is the most conservative mode and the one that will result in the most number of Pokemon kept. + +- `"by_family"` will apply the rule for each Pokemon family. +
In that mode, each Pokemon will be compared to other Pokemon of the same family. + +- `"overall"` will apply the rule to the whole bag. +
In that mode, each Pokemon will be compared to all other Pokemon in the bag. +
This is the most aggressive mode and will result in the less number of Pokemon kept. + +[[back to top](#pokemon-optimizer)] + +#### keep names +| Parameter | Possible values | Default | +|-----------|-----------------|--------------------------| +| `names` | list of strings | `[]` = All Pokemon names | + +The `names` allow you to restrict a rule to a selected set of Pokemon. +
It is a list of Pokemon names or Pokemon [`groups`](#groups). +
You can negate a name by preceding it by a `!` or `-`. In that case, the rule apply to all except the negated names. +
You can combine Pokemon names and group names together in the same list. + +By default, rules apply to all Pokemons. +- In `by_family` mode, if a Pokemon name is present in the `names` list, it refer to all Pokemon in that Pokemon family. +- In `overall` mode, the `names` list behave as a filter on the whole Pokemon bag. + +[[back to top](#pokemon-optimizer)] + +#### keep top +| Parameter | Possible values | Default | +|-----------|-----------------------|---------| +| `top` | `0`, `]0-1[`, `[1-N]` | `0` | + +This value define how many Pokemon, at the top of your selection, you wish to keep. + +- If the value `N` is an integer greater or equal to `1`, it means you wish to keep the N best Pokemon of your selection. +
Pokemon will be sorted according to your sorting rule and the `Nth` first will be kept. +
In case of equality between Pokemon, it may result in more than `N` Pokemon selected, but never less. +
See examples here + +- If the value `N` is a decimal between `0` and `1`, it is a ratio relative to the best Pokemon. +
Pokemon will be sorted according to your sorting rule and the best will be elected. +
All Pokemon whose criteria does not deviate more than `N*100 %` of the best will also be selected. +
See examples here + +- If the value `N` is `0` or negative, all Pokemon in the selected will be kept. +
See examples here + +[[back to top](#pokemon-optimizer)] + +#### keep sort +| Parameter | Possible values | Default | +|-----------|-----------------|---------| +| `sort` | (see below) | `[]` | + +Define according to which criteria you want to sort your Pokemon. +
Available criteria are: + +| Criteria | Description | +|----------------------|------------------------------------------------------------------------------| +| `iv` | individual value between 0 and 1 | +| `ivcp` | iv weighted so that for equal iv, attack > defense > stamina | +| `cp` | combat power (can be increased with candies) | +| `cp_exact` | combat power (not rounded) | +| `ncp` | normalized cp = ratio cp / max_cp | +| `iv_attack` | attack component of iv between 0 and 15 | +| `iv_defense` | defense component of iv between 0 and 15 | +| `iv_stamina` | stamina component of iv between 0 and 15 | +| `dps` | raw dps based on the moves of the pokemon | +| `dps_attack` | estimated average dps when attacking | +| `attack_perfection` | ratio `dps_attack` / `best_dps_attack`. Return same order as `dps_attack` | +| `dps_defense` | estimated average dps when defending | +| `defense_perfection` | ratio `dps_defense` / `best_dps_defense`. Return same order as `dps_defense` | +| `hp` | current health points | +| `hp_max` | max health points | + +You can put multiple criteria in the list by separating them by a comma: `"sort": ["iv", "cp"]` +
If multiple criteria are present, Pokemon will be ranked according to the first, then if equals the second, etc... + +In addition to define sorting criteria, the `sort` list may contain minimum requirements for evolution: +- `"top": 1, "sort": ["iv"], "evolve": True` will rank Pokemon by `iv`. +
It will keep and evolve the best of them. +- `"top": 1, "sort": [{"iv": 0.9}], "evolve": True` will rank Pokemon by `iv`. +
It will keep the best of them and evolve it only if its `iv` is greater than `0.9`. +- `"top": 3, "sort": [{"iv": 0.9}, {"cp": 1200}], "evolve": True` will rank Pokemon by `iv` and then `cp`. +
It will keep the top 3 of them and evolve them only if their `iv` is greater than `0.9` and their `cp` greater than `1200`. + +[[back to top](#pokemon-optimizer)] + +#### keep evolve +| Parameter | Possible values | Default | +|-----------|-----------------|---------| +| `evolve` | `true`, `false` | `true` | + +The parameter allow you to selectively control what Pokemon to evolve. + +At `true`, all Pokemon selected and meeting the minimum evolution criteria (see [`sort`](#keep-sort)) will be evolved. +
At `false`, Pokemon selected will not be eligible for evolution, unless they are also selected by another rule that allow them to evolve. + +[[back to top](#pokemon-optimizer)] + +## Examples of configuration +> Keep the 2 best `iv` of every single Pokemon, and evolve them if they are over 0.9 `iv`. + +``` +{ + "mode": "by_pokemon", + "top": 2, + "sort": [{"iv": 0.9}], + "evolve": true +}, +``` + +> Keep my 10 best `cp` Dragonite and Snorlax to fight gyms. + +``` +{ + "mode": "by_pokemon", + "names": ["Dragonite", "Snorlax"] + "top": 10, + "sort": ["cp"], + "evolve": false +}, +``` + +> Keep my Gyarados with the best moveset for attack. + +``` +{ + "mode": "by_pokemon", + "names": ["Gyarados"] + "top": 1, + "sort": ["dps_attack"], + "evolve": false +}, +``` + +> I don't want you to use my candies for my loved Pokemon, But for others, it is ok. + +``` +"groups": { + "my loves ones" : ["Dragonite", "Snorlax", "Caterpie"] +}, +"keep": [ + { + "mode": "by_family", + "names": ["my loves ones"] + "top": 1, + "sort": ["iv"], + "evolve": false + }, + { + "mode": "by_family", + "names": ["!my loves ones"] + "top": 1, + "sort": ["iv"], + "evolve": true + } + // + Exclude `my loves ones` from any other rule +] +``` + +> Do not touch my Dragonites ! + +``` +{ + "mode": "by_pokemon", + "names": ["Dragonite"] +}, +// + Exclude Dragonite from any other rule +``` + +[[back to top](#pokemon-optimizer)] + +# Eevee case + +For Eevee Pokemon family, and any other family with multiple paths of evolution, the Pokemon Optimizer behaves as if the chances of getting a specific evolution were random and equal. +
In practice, here are the effects you might notice regarding Eevee family: +- If you are missing one version of evolution, every Eevee is a possible candidate to the become the best Eevee you have for that specific evolution. +
So as long as an evolution version is missing, the Pokemon Optimizer will tentatively try to keep and evolve all Eevees. + +- Once you have all version of evolution, things are not yet simple. Every every better than the worst evolution you have is a candidate to replace it. +
The Pokemon Optimizer will tentatively try to keep and evolve all Eevees that may replace the worst evolution you have. + +- If you deactivate the global `evolve` parameter, the Pokemon Optimizer will not apply above rules since it considers you are manually controlling the evolution of your Eevees. + + +[[back to top](#pokemon-optimizer)] diff --git a/pokemongo_bot/base_task.py b/pokemongo_bot/base_task.py index da671776bd..151b9006a5 100644 --- a/pokemongo_bot/base_task.py +++ b/pokemongo_bot/base_task.py @@ -33,7 +33,7 @@ def emit_event(self, event, sender=None, level='info', formatted='', data={}): # Print log only if X seconds are passed from last log try: - if (time.time() - self.last_log_time) > self.config.get('log_interval', 0): + if (time.time() - self.last_log_time) >= self.config.get('log_interval', 0): self.last_log_time = time.time() self.bot.event_manager.emit( event, diff --git a/pokemongo_bot/cell_workers/pokemon_optimizer.py b/pokemongo_bot/cell_workers/pokemon_optimizer.py index 41b1c06f96..8c7147109e 100644 --- a/pokemongo_bot/cell_workers/pokemon_optimizer.py +++ b/pokemongo_bot/cell_workers/pokemon_optimizer.py @@ -1,10 +1,8 @@ -import copy -import json +import difflib +import itertools import math -import os from pokemongo_bot import inventory -from pokemongo_bot.base_dir import _base_dir from pokemongo_bot.base_task import BaseTask from pokemongo_bot.datastore import Datastore from pokemongo_bot.human_behaviour import sleep, action_delay @@ -25,23 +23,23 @@ def __init__(self, bot, config): super(PokemonOptimizer, self).__init__(bot, config) def initialize(self): - self.family_by_family_id = {} self.max_pokemon_storage = inventory.get_pokemon_inventory_size() self.last_pokemon_count = 0 + self.pokemon_names = [p.name for p in inventory.pokemons().STATIC_DATA] self.config_transfer = self.config.get("transfer", False) + self.config_transfer_wait_min = self.config.get("transfer_wait_min", 3) + self.config_transfer_wait_max = self.config.get("transfer_wait_max", 5) self.config_evolve = self.config.get("evolve", False) - self.config_evolve_time = self.config.get("evolve_time", 20) + self.config_evolve_time = self.config.get("evolve_time", 25) self.config_evolve_for_xp = self.config.get("evolve_for_xp", True) self.config_evolve_only_with_lucky_egg = self.config.get("evolve_only_with_lucky_egg", False) - self.config_evolve_count_for_lucky_egg = self.config.get("evolve_count_for_lucky_egg", 92) + self.config_evolve_count_for_lucky_egg = self.config.get("evolve_count_for_lucky_egg", 80) self.config_may_use_lucky_egg = self.config.get("may_use_lucky_egg", False) - self.config_keep = self.config.get("keep", [{"top": 1, "evolve": True, "sort": ["iv"]}, - {"top": 1, "evolve": True, "sort": ["ncp"]}, - {"top": 1, "evolve": False, "sort": ["cp"]}]) - - self.config_transfer_wait_min = self.config.get("transfer_wait_min", 1) - self.config_transfer_wait_max = self.config.get("transfer_wait_max", 4) + self.config_groups = self.config.get("groups", {}) + self.config_keep = self.config.get("keep", [{"mode": "by_family", "top": 1, "sort": [{"iv": 0.9}], "evolve": True}, + {"mode": "by_family", "top": 1, "sort": [{"ncp": 0.9}], "evolve": True}, + {"mode": "by_family", "top": 1, "sort": ["cp"], "evolve": False}]) if (not self.config_may_use_lucky_egg) and self.config_evolve_only_with_lucky_egg: self.config_evolve = False @@ -52,7 +50,7 @@ def get_pokemon_slot_left(self): if pokemon_count != self.last_pokemon_count: self.last_pokemon_count = pokemon_count self.logger.info("Pokemon Bag: %s/%s", pokemon_count, self.max_pokemon_storage) - self.save_web_inventory() + inventory.update_web_inventory() return inventory.Pokemons.get_space_left() @@ -62,147 +60,265 @@ def work(self): self.open_inventory() + try_evolve_all = [] + keep_all = [] + + for rule in self.config_keep: + mode = rule.get("mode", "by_family") + names = rule.get("names", []) + whitelist_names, blacklist_names = self.get_colorlist_names(names) + + if mode == "by_pokemon": + for pokemon_id, pokemon_list in self.group_by_pokemon_id(inventory.pokemons().all()): + name = inventory.pokemons().name_for(pokemon_id) + + if name in blacklist_names: + continue + + if whitelist_names and (name not in whitelist_names): + continue + + try_evolve, keep = self.get_best_pokemon_for_rule(pokemon_list, rule) + try_evolve_all += try_evolve + keep_all += keep + elif mode == "by_family": + for family_id, pokemon_list in self.group_by_family_id(inventory.pokemons().all()): + matching_names = self.get_family_names(family_id) + + if any(n in blacklist_names for n in matching_names): + continue + + if whitelist_names and not any(n in whitelist_names for n in matching_names): + continue + + if family_id == 133: # "Eevee" + try_evolve, keep = self.get_multi_best_pokemon_for_rule(pokemon_list, rule, 3) + else: + try_evolve, keep = self.get_best_pokemon_for_rule(pokemon_list, rule) + + try_evolve_all += try_evolve + keep_all += keep + elif mode == "overall": + pokemon_list = [] + + for pokemon in inventory.pokemons().all(): + name = pokemon.name + + if name in blacklist_names: + continue + + if whitelist_names and (name not in whitelist_names): + continue + + pokemon_list.append(pokemon) + + try_evolve, keep = self.get_best_pokemon_for_rule(pokemon_list, rule) + try_evolve_all += try_evolve + keep_all += keep + + try_evolve_all = self.unique_pokemon_list(try_evolve_all) + keep_all = [p for p in self.unique_pokemon_list(keep_all) if p not in try_evolve_all] + transfer_all = [] - evo_all_best = [] - evo_all_crap = [] + evolve_all = [] + xp_all = [] - for family_id, family in self.family_by_family_id.items(): - if family_id == 133: # "Eevee" - transfer, evo_best, evo_crap = self.get_multi_family_optimized(family_id, family, 3) - else: - transfer, evo_best, evo_crap = self.get_family_optimized(family_id, family) + for family_id, pokemon_list in self.group_by_family_id(inventory.pokemons().all()): + try_evolve = [p for p in try_evolve_all if self.get_family_id(p) == family_id] + keep = [p for p in keep_all if self.get_family_id(p) == family_id] - transfer_all += transfer - evo_all_best += evo_best - evo_all_crap += evo_crap + transfer, evolve, xp = self.get_evolution_plan(family_id, pokemon_list, try_evolve, keep) - evo_all = evo_all_best + evo_all_crap + transfer_all += transfer + evolve_all += evolve + xp_all += xp - self.apply_optimization(transfer_all, evo_all) - self.save_web_inventory() + self.apply_optimization(transfer_all, evolve_all, xp_all) + inventory.update_web_inventory() return WorkerResult.SUCCESS def open_inventory(self): - self.family_by_family_id.clear() - for pokemon in inventory.pokemons().all(): - family_id = pokemon.first_evolution_id setattr(pokemon, "ncp", pokemon.cp_percent) setattr(pokemon, "dps", pokemon.moveset.dps) setattr(pokemon, "dps_attack", pokemon.moveset.dps_attack) setattr(pokemon, "dps_defense", pokemon.moveset.dps_defense) - self.family_by_family_id.setdefault(family_id, []).append(pokemon) + def get_colorlist_names(self, names): + whitelist_names = [] + blacklist_names = [] - def save_web_inventory(self): - inventory.update_web_inventory() + for name in names: + if not name: + continue - def get_family_optimized(self, family_id, family): - evolve_best = [] - keep_best = [] - family_names = self.get_family_names(family_id) + if name[0] not in ['!', '-']: + group_names = self.config_groups.get(name, []) - for criteria in self.config_keep: - names = criteria.get("names", []) + if not group_names: + name = self.get_closest_name(name) - if names and not any(n in family_names for n in names): - continue - - if criteria.get("evolve", True): - evolve_best += self.get_top_rank(family, criteria) + if name: + whitelist_names.append(name) + whitelist_subnames, blacklist_subnames = self.get_colorlist_names(group_names) + whitelist_names += whitelist_subnames + blacklist_names += blacklist_subnames else: - keep_best += self.get_top_rank(family, criteria) + name = name[1:] + group_names = self.config_groups.get(name, []) - evolve_best = self.unique_pokemon(evolve_best) - keep_best = self.unique_pokemon(keep_best) + if not group_names: + name = self.get_closest_name(name) - return self.get_evolution_plan(family_id, family, evolve_best, keep_best) + if name: + blacklist_names.append(name) + blacklist_subnames, whitelist_subnames = self.get_colorlist_names(group_names) + blacklist_names += blacklist_subnames + whitelist_names += whitelist_subnames - def get_multi_family_optimized(self, family_id, family, nb_branch): - # Transfer each group of senior independently - senior_family = [p for p in family if not p.has_next_evolution()] - other_family = [p for p in family if p.has_next_evolution()] - senior_pids = set(p.pokemon_id for p in senior_family) - senior_grouped_family = {pid: [p for p in senior_family if p.pokemon_id == pid] for pid in senior_pids} + return (whitelist_names, blacklist_names) - if not self.config_evolve: - transfer, evo_best, evo_crap = self.get_family_optimized(family_id, other_family) - elif len(senior_pids) < nb_branch: - # We did not get every combination yet = All other Pokemon are potentially good to keep - transfer, evo_best, evo_crap = self.get_evolution_plan(family_id, [], other_family, []) - evo_best.sort(key=lambda p: p.iv * p.ncp, reverse=True) + def get_family_names(self, family_id): + ids = [family_id] + ids += inventory.pokemons().data_for(family_id).next_evolutions_all[:] + return [inventory.pokemons().name_for(x) for x in ids] + + def get_closest_name(self, name): + closest_names = difflib.get_close_matches(name, self.pokemon_names, 1) + + if closest_names: + closest_name = closest_names[0] + + if name != closest_name: + self.logger.warning("Unknown Pokemon name [%s]. Assuming it is [%s]", name, closest_name) + + return closest_name else: - evolve_best = [] - keep_best = [] - names = self.get_family_names(family_id) + self.logger.error("Unknown Pokemon name [%s]", name) + return "" - for criteria in self.config_keep: - family_names = criteria.get("names", []) + def group_by_pokemon_id(self, pokemon_list): + sorted_list = sorted(pokemon_list, key=self.get_pokemon_id) + return itertools.groupby(sorted_list, self.get_pokemon_id) - if names and not any(n in family_names for n in names): - continue + def group_by_family_id(self, pokemon_list): + sorted_list = sorted(pokemon_list, key=self.get_family_id) + return itertools.groupby(sorted_list, self.get_family_id) - top = [] + def get_pokemon_id(self, pokemon): + return pokemon.pokemon_id - for f in senior_grouped_family.values(): - top += self.get_top_rank(f, criteria) + def get_family_id(self, pokemon): + return pokemon.first_evolution_id - worst = self.get_sorted_family(top, criteria)[-1] + def get_best_pokemon_for_rule(self, pokemon_list, rule): + sorted_pokemon = self.sort_pokemon_list(pokemon_list, rule) - if criteria.get("evolve", True): - evolve_best += self.get_better_rank(family, criteria, worst) - else: - keep_best += self.get_better_rank(family, criteria, worst) + if len(sorted_pokemon) == 0: + return ([], []) - evolve_best = self.unique_pokemon(evolve_best) - keep_best = self.unique_pokemon(keep_best) - transfer, evo_best, evo_crap = self.get_evolution_plan(family_id, other_family, evolve_best, keep_best) + top = max(rule.get("top", 1), 0) + index = int(math.ceil(top)) - 1 - for senior_pid, senior_family in senior_grouped_family.items(): - transfer += self.get_family_optimized(senior_pid, senior_family)[0] + if 0 < top < 1: + worst = object() - return (transfer, evo_best, evo_crap) + for a in rule.get("sort"): + best_attribute = getattr(sorted_pokemon[0], a) + setattr(worst, a, best_attribute * (1 - top)) + elif 0 <= index < len(sorted_pokemon): + worst = sorted_pokemon[index] + else: + worst = sorted_pokemon[-1] - def get_family_names(self, family_id): - ids = [family_id] - ids += inventory.Pokemons.data_for(family_id).next_evolutions_all[:] - datas = [inventory.Pokemons.data_for(x) for x in ids] - return [x.name for x in datas] + return self.get_better_pokemon_for_rule(sorted_pokemon, rule, worst) + + def get_multi_best_pokemon_for_rule(self, family_list, rule, nb_branch): + sorted_family = self.sort_pokemon_list(family_list, rule) - def get_top_rank(self, family, criteria): - sorted_family = self.get_sorted_family(family, criteria) - index = criteria.get("top", 1) - 1 + # Handle each group of senior independently + senior_pokemon_list = [p for p in sorted_family if not p.has_next_evolution()] + other_family_list = [p for p in sorted_family if p.has_next_evolution()] + senior_pids = set(p.pokemon_id for p in senior_pokemon_list) - if 0 <= index < len(sorted_family): - worst = sorted_family[index] - return [p for p in sorted_family if self.get_rank(p, criteria) >= self.get_rank(worst, criteria)] + try_evolve_all = [] + keep_all = [] + + if not self.config_evolve: + # Player handle evolution manually = Fall-back to per Pokemon behavior + for _, pokemon_list in self.group_by_pokemon_id(sorted_family): + try_evolve, keep = self.get_best_pokemon_for_rule(pokemon_list, rule) + try_evolve_all += try_evolve + keep_all += keep + elif len(senior_pids) < nb_branch: + # We did not get every combination yet = All other Pokemon are potentially good to keep + for _, pokemon_list in self.group_by_pokemon_id(senior_pokemon_list): + try_evolve, keep = self.get_best_pokemon_for_rule(pokemon_list, rule) + try_evolve_all += try_evolve + keep_all += keep + + try_evolve, keep = self.sort_pokemon_list(other_family_list, rule) + try_evolve_all += try_evolve + keep_all += keep else: - return sorted_family + best = [] + + for _, pokemon_list in self.group_by_pokemon_id(senior_pokemon_list): + try_evolve, keep = self.get_best_pokemon_for_rule(pokemon_list, rule) + best += try_evolve + best += keep + + worst = self.sort_pokemon_list(best, rule)[-1] + + try_evolve_all, keep_all = self.get_better_pokemon_for_rule(sorted_family, rule, worst) + + return try_evolve_all, keep_all + + def get_better_pokemon_for_rule(self, pokemon_list, rule, worst): + min_score = self.get_score(worst, rule)[0] + scored_list = [(p, self.get_score(p, rule)) for p in pokemon_list] + better = [x for x in scored_list if x[1][0] >= min_score] + try_evolve = [x[0] for x in better if x[1][1] is True] + keep = [x[0] for x in better if x[1][1] is False] + + return try_evolve, keep - def get_better_rank(self, family, criteria, worst): - return [p for p in self.get_sorted_family(family, criteria) if self.get_rank(p, criteria) >= self.get_rank(worst, criteria)] + def sort_pokemon_list(self, pokemon_list, rule): + return sorted(pokemon_list, key=lambda p: self.get_score(p, rule)[0], reverse=True) - def get_sorted_family(self, family, criteria): - return sorted(family, key=lambda p: self.get_rank(p, criteria), reverse=True) + def get_score(self, pokemon, rule): + score = [] + may_try_evolve = (getattr(pokemon, "has_next_evolution", None) and + pokemon.has_next_evolution() and + rule.get("evolve", True)) - def get_rank(self, pokemon, criteria): - return tuple(getattr(pokemon, a, None) for a in criteria.get("sort")) + for a in rule.get("sort"): + if (type(a) is str) or (type(a) is unicode): + value = getattr(pokemon, a, 0) + score.append(value) + elif type(a) is dict: + value = getattr(pokemon, a.keys()[0], 0) + score.append(value) + may_try_evolve &= (value >= a.values()[0]) + elif type(a) is list: + value = getattr(pokemon, a[0], 0) + score.append(value) + may_try_evolve &= (value >= a[1]) - def get_pokemon_max_cp(self, pokemon_name): - return int(self.pokemon_max_cp.get(pokemon_name, 0)) + return tuple(score), may_try_evolve - def unique_pokemon(self, l): + def unique_pokemon_list(self, pokemon_list): seen = set() - return [p for p in l if not (p.unique_id in seen or seen.add(p.unique_id))] + return [p for p in pokemon_list if not (p.unique_id in seen or seen.add(p.unique_id))] - def get_evolution_plan(self, family_id, family, evolve_best, keep_best): + def get_evolution_plan(self, family_id, family_list, try_evolve, keep): candies = inventory.candies().get(family_id).quantity # All the rest is crap, for now - crap = family[:] - crap = [p for p in crap if p not in evolve_best] - crap = [p for p in crap if p not in keep_best] + crap = list(family_list) + crap = [p for p in crap if p not in try_evolve] + crap = [p for p in crap if p not in keep] crap = [p for p in crap if not p.in_fort and not p.is_favorite] crap.sort(key=lambda p: p.iv * p.ncp, reverse=True) @@ -210,63 +326,52 @@ def get_evolution_plan(self, family_id, family, evolve_best, keep_best): candies += len(crap) # Let's see if we can evolve our best Pokemon - can_evolve_best = [] - - for pokemon in evolve_best: - if not pokemon.has_next_evolution(): - continue + evolve = [] + for pokemon in try_evolve: candies -= pokemon.evolution_cost if candies < 0: break candies += 1 - can_evolve_best.append(pokemon) - - # Not sure if the evo keep the same id - next_pid = pokemon.next_evolution_ids[0] - next_evo = copy.copy(pokemon) - next_evo.pokemon_id = next_pid - next_evo.static = inventory.pokemons().data_for(next_pid) - next_evo.name = inventory.pokemons().name_for(next_pid) - evolve_best.append(next_evo) + evolve.append(pokemon) if self.config_evolve_for_xp: # Compute how many crap we should keep if we want to batch evolve them for xp - junior_evolution_cost = inventory.pokemons().evolution_cost_for(family_id) + lowest_evolution_cost = inventory.pokemons().evolution_cost_for(family_id) - # transfer + keep_for_evo = len(crap) + # transfer + keep_for_xp = len(crap) # leftover_candies = candies - len(crap) + transfer * 1 - # keep_for_evo = (leftover_candies - 1) / (junior_evolution_cost - 1) - # keep_for_evo = (candies - len(crap) + transfer - 1) / (junior_evolution_cost - 1) - # keep_for_evo = (candies - keep_for_evo - 1) / (junior_evolution_cost - 1) + # keep_for_xp = (leftover_candies - 1) / (lowest_evolution_cost - 1) + # keep_for_xp = (candies - len(crap) + transfer - 1) / (lowest_evolution_cost - 1) + # keep_for_xp = (candies - keep_for_xp - 1) / (lowest_evolution_cost - 1) - if (candies > 0) and junior_evolution_cost: - keep_for_evo = int((candies - 1) / junior_evolution_cost) + if (candies > 0) and lowest_evolution_cost: + keep_for_xp = int((candies - 1) / lowest_evolution_cost) else: - keep_for_evo = 0 + keep_for_xp = 0 - evo_crap = [p for p in crap if p.has_next_evolution() and p.evolution_cost == junior_evolution_cost][:keep_for_evo] + xp = [p for p in crap if p.has_next_evolution() and p.evolution_cost == lowest_evolution_cost][:keep_for_xp] # If not much to evolve, better keep the candies - if len(evo_crap) < math.ceil(self.max_pokemon_storage * 0.01): - evo_crap = [] + if len(xp) < math.ceil(self.max_pokemon_storage * 0.02): + xp = [] - transfer = [p for p in crap if p not in evo_crap] + transfer = [p for p in crap if p not in xp] else: - evo_crap = [] + xp = [] transfer = crap - return (transfer, can_evolve_best, evo_crap) + return (transfer, evolve, xp) - def apply_optimization(self, transfer, evo): + def apply_optimization(self, transfer, evolve, xp): self.logger.info("Transferring %s Pokemon", len(transfer)) for pokemon in transfer: self.transfer_pokemon(pokemon) - if len(evo) == 0: + if len(evolve) + len(xp) == 0: return if self.config_evolve and self.config_may_use_lucky_egg and (not self.bot.config.test): @@ -277,21 +382,26 @@ def apply_optimization(self, transfer, evo): self.emit_event("skip_evolve", formatted="Skipping evolution step. No lucky egg available") return - elif len(evo) < self.config_evolve_count_for_lucky_egg: + elif (len(evolve) + len(xp)) < self.config_evolve_count_for_lucky_egg: if self.config_evolve_only_with_lucky_egg: self.emit_event("skip_evolve", - formatted="Skipping evolution step. Not enough Pokemon to evolve with lucky egg: %s/%s" % (len(evo), self.config_evolve_count_for_lucky_egg)) + formatted="Skipping evolution step. Not enough Pokemon to evolve with lucky egg: %s/%s" % (len(evolve) + len(xp), self.config_evolve_count_for_lucky_egg)) return elif self.get_pokemon_slot_left() > 5: self.emit_event("skip_evolve", - formatted="Waiting for more Pokemon to evolve with lucky egg: %s/%s" % (len(evo), self.config_evolve_count_for_lucky_egg)) + formatted="Waiting for more Pokemon to evolve with lucky egg: %s/%s" % (len(evolve) + len(xp), self.config_evolve_count_for_lucky_egg)) return else: self.use_lucky_egg() - self.logger.info("Evolving %s Pokemon", len(evo)) + self.logger.info("Evolving %s Pokemon (the best)", len(evolve)) + + for pokemon in evolve: + self.evolve_pokemon(pokemon) + + self.logger.info("Evolving %s Pokemon (for xp)", len(xp)) - for pokemon in evo: + for pokemon in xp: self.evolve_pokemon(pokemon) def transfer_pokemon(self, pokemon): @@ -370,7 +480,7 @@ def evolve_pokemon(self, pokemon): if self.config_evolve and (not self.bot.config.test): response_dict = self.bot.api.evolve_pokemon(pokemon_id=pokemon.unique_id) else: - response_dict = {"responses": {"EVOLVE_POKEMON": {"result": 1}}} + response_dict = {"responses": {"EVOLVE_POKEMON": {"result": SUCCESS}}} if not response_dict: return False @@ -411,6 +521,6 @@ def evolve_pokemon(self, pokemon): if db_result[0] == 1: db.execute("INSERT INTO evolve_log (pokemon, iv, cp) VALUES (?, ?, ?)", (pokemon.name, pokemon.iv, pokemon.cp)) - sleep(self.config_evolve_time) + sleep(self.config_evolve_time, 0.1) return True From 56a0d11c30d06c055076ac1bb37346ea948d542e Mon Sep 17 00:00:00 2001 From: Ovidiu Grigore Ghinet Date: Tue, 30 Aug 2016 10:50:17 +0200 Subject: [PATCH 27/38] documented docker for the auth.json use case (#4922) add instructions for the docker run command for the multiple config files use case. --- docs/installation.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/installation.md b/docs/installation.md index 36424a0405..3919924584 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -83,6 +83,19 @@ To run the bot container with the PokemonGo-Bot Docker image you've created: docker run --name=bot1-pokego --rm -it -v $(pwd)/configs/config.json:/usr/src/app/configs/config.json pokemongo-bot ``` +>In the case you configured authentification to be handled by auth.json file make sure you mount that file as a volume also + +>``` +docker run --name=bot1-pokego --rm -it -v $(pwd)/configs/auth.json:/usr/src/app/configs/auth.json -v $(pwd)/configs/config.json:/usr/src/app/configs/config.json -v $(pwd)/web/:/usr/src/app/web/ pokemongo-bot +``` + +>or for a simplified version mount your whole configs/ subdir to /usr/src/app/configs + +>``` +docker run --name=bot1-pokego --rm -it -v $(pwd)/configs:/usr/src/app/configs -v $(pwd)/web/:/usr/src/app/web/ pokemongo-bot +``` + + Run a second container provided with the OpenPoGoBotWeb view: ``` From daca13f08203664df9dd37437579d7b5fe637650 Mon Sep 17 00:00:00 2001 From: Matt J Madsen Date: Tue, 30 Aug 2016 04:27:03 -0500 Subject: [PATCH 28/38] Fix for #4718 (#4924) * Add except variable * Add except variable --- pokemongo_bot/cell_workers/telegram_task.py | 2 +- pokemongo_bot/inventory.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pokemongo_bot/cell_workers/telegram_task.py b/pokemongo_bot/cell_workers/telegram_task.py index f9c421aff5..c5bdd2ae58 100644 --- a/pokemongo_bot/cell_workers/telegram_task.py +++ b/pokemongo_bot/cell_workers/telegram_task.py @@ -102,7 +102,7 @@ def _get_player_stats(self): try: with open(web_inventory, "r") as infile: json_inventory = json.load(infile) - except ValueError: + except ValueError as e: # 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) diff --git a/pokemongo_bot/inventory.py b/pokemongo_bot/inventory.py index d4a2efc5f3..461de2b6f7 100644 --- a/pokemongo_bot/inventory.py +++ b/pokemongo_bot/inventory.py @@ -1211,7 +1211,7 @@ def update_web_inventory(self): try: with open(web_inventory, "w") as outfile: json.dump(json_inventory, outfile) - except (IOError, ValueError): + except (IOError, ValueError) as e: self.bot.logger.info('[x] Error while opening inventory file for write: %s' % e, 'red') pass except: From 3323a752fba7b0f0bbe26c19e73618aeb864a745 Mon Sep 17 00:00:00 2001 From: Matt J Madsen Date: Tue, 30 Aug 2016 04:55:22 -0500 Subject: [PATCH 29/38] Fix filter (#4925) --- pokemongo_bot/cell_workers/evolve_pokemon.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pokemongo_bot/cell_workers/evolve_pokemon.py b/pokemongo_bot/cell_workers/evolve_pokemon.py index d1177050ac..48ea4316ed 100644 --- a/pokemongo_bot/cell_workers/evolve_pokemon.py +++ b/pokemongo_bot/cell_workers/evolve_pokemon.py @@ -45,6 +45,9 @@ def work(self): filtered_list = self._sort_and_filter() + if (len(self.evolve_list) > 0) and self.evolve_list[0] != 'all': + filtered_list = filter(lambda x: x.name in self.evolve_list, filtered_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) From 85545e5fd9c7892b03b2e8d845afbaff6a58b052 Mon Sep 17 00:00:00 2001 From: walkerlee Date: Tue, 30 Aug 2016 17:56:43 +0800 Subject: [PATCH 30/38] improve docker pull speed (#4899) --- Dockerfile | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/Dockerfile b/Dockerfile index cd3830b7fd..36e182d8b4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -23,24 +23,23 @@ LABEL build_repo=$BUILD_REPO build_branch=$BUILD_BRANCH WORKDIR /usr/src/app 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 \ - && 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 \ + && rm -rf /var/cache/apk/* \ && 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 \ +ADD https://raw.githubusercontent.com/$BUILD_REPO/$BUILD_BRANCH/requirements.txt . +RUN apk -U --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 \ && 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 + && 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/* /tmp/pgoencrypt* /usr/include/xlocale.h \ + && find / -name '*.pyc' -o -name '*.pyo' | xargs -rn1 rm -f 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 \ From 9e81c6ed90d79e181599ec7f0a0cfa2ecd4d09f5 Mon Sep 17 00:00:00 2001 From: Nikolay Spiridonov Date: Tue, 30 Aug 2016 14:42:37 +0400 Subject: [PATCH 31/38] Update inventory.py (#4928) FIX: #4926 --- pokemongo_bot/inventory.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/pokemongo_bot/inventory.py b/pokemongo_bot/inventory.py index 461de2b6f7..b09d1c5330 100644 --- a/pokemongo_bot/inventory.py +++ b/pokemongo_bot/inventory.py @@ -128,11 +128,14 @@ def refresh(self,inventory): self.player_stats = self.retrieve_data(inventory) 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'] + if not item: + item = {} + + self.exp = item.get('experience', 0) + self.level = item.get('level', 0) + self.next_level_xp = item.get('next_level_xp', 0) + self.pokemons_captured = item.get('pokemons_captured', 0) + self.poke_stop_visits = item.get('poke_stop_visits', 0) def retrieve_data(self, inventory): ret = {} From 5858a02435e19ffb9b3641f24cea0d6c0a43c1be Mon Sep 17 00:00:00 2001 From: DBa2016 Date: Tue, 30 Aug 2016 13:46:11 +0200 Subject: [PATCH 32/38] fixed a runtime error caused by incorrect imports (#4931) --- pokemongo_bot/cell_workers/telegram_task.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pokemongo_bot/cell_workers/telegram_task.py b/pokemongo_bot/cell_workers/telegram_task.py index c5bdd2ae58..c27b9eb1fe 100644 --- a/pokemongo_bot/cell_workers/telegram_task.py +++ b/pokemongo_bot/cell_workers/telegram_task.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- -import datetime +from datetime import datetime +from datetime import timedelta import telegram import os import logging From 47e1ddd1e0a173cdade790981891a360d5cba42b Mon Sep 17 00:00:00 2001 From: Simba Zhang Date: Tue, 30 Aug 2016 07:38:26 -0700 Subject: [PATCH 33/38] Catch exception telegram.error.NetworkError. Fixs some pylint complain. --- pokemongo_bot/cell_workers/telegram_task.py | 54 +++++++++++---------- 1 file changed, 29 insertions(+), 25 deletions(-) diff --git a/pokemongo_bot/cell_workers/telegram_task.py b/pokemongo_bot/cell_workers/telegram_task.py index 5c2b60787e..9ec8f2faf4 100644 --- a/pokemongo_bot/cell_workers/telegram_task.py +++ b/pokemongo_bot/cell_workers/telegram_task.py @@ -1,15 +1,12 @@ # -*- coding: utf-8 -*- -import telegram import os import logging import json +import telegram from pokemongo_bot.base_task import BaseTask from pokemongo_bot.base_dir import _base_dir from pokemongo_bot.event_handlers import TelegramHandler -from pprint import pprint -import re - class FileIOException(Exception): pass @@ -22,15 +19,16 @@ def initialize(self): if not self.enabled: return api_key = self.bot.config.telegram_token - if api_key == None: + if api_key is None: self.emit_event( 'config_error', formatted='api_key not defined.' ) return self.tbot = telegram.Bot(api_key) - if self.config.get('master',None): - self.bot.event_manager.add_handler(TelegramHandler(self.tbot,self.config.get('master',None),self.config.get('alert_catch'))) + self.master = self.config.get('master', None) + if self.master: + self.bot.event_manager.add_handler(TelegramHandler(self.tbot, self.master, self.config.get('alert_catch'))) try: self.update_id = self.tbot.getUpdates()[0].update_id except IndexError: @@ -43,19 +41,16 @@ def work(self): self.update_id = update.update_id+1 if update.message: 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', - formatted="Master wrong: expecting {}, got {}({})".format(self.config.get('master',None), update.message.from_user.username, update.message.from_user.id)) + 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', + formatted="Master wrong: expecting {}, got {}({})".format(self.master, update.message.from_user.username, update.message.from_user.id)) continue else: if not re.match(r'^[0-9]+$', "{}".format(self.config['master'])): # master was not numeric... self.config['master'] = update.message.chat_id idx = (i for i,v in enumerate(self.bot.event_manager._handlers) if type(v) is TelegramHandler).next() - self.bot.event_manager._handlers[idx] = TelegramHandler(self.tbot,self.config['master'], self.config.get('alert_catch')) - - - + self.bot.event_manager._handlers[idx] = TelegramHandler(self.tbot, self.master, self.config.get('alert_catch')) if update.message.text == "/info": stats = self._get_player_stats() if stats: @@ -73,17 +68,26 @@ def work(self): "_Poke Stop Visits:_ "+str(stats["poke_stop_visits"])+" ("+str(ps_day)+" _last 24h_)", "_KM Walked:_ "+str(stats["km_walked"]) ) - self.tbot.sendMessage(chat_id=update.message.chat_id, parse_mode='Markdown', text="\n".join(res)) - self.tbot.send_location(chat_id=update.message.chat_id, latitude=self.bot.api._position_lat, longitude=self.bot.api._position_lng) + self.send_message(chat_id=update.message.chat_id, parse_mode='Markdown', text="\n".join(res)) + self.send_location(chat_id=update.message.chat_id, latitude=self.bot.api._position_lat, longitude=self.bot.api._position_lng) else: - self.tbot.sendMessage(chat_id=update.message.chat_id, parse_mode='Markdown', text="Stats not loaded yet\n") + self.send_message(chat_id=update.message.chat_id, parse_mode='Markdown', text="Stats not loaded yet\n") elif update.message.text == "/start" or update.message.text == "/help": res = ( "Commands: ", "/info - info about bot" ) - self.tbot.sendMessage(chat_id=update.message.chat_id, parse_mode='Markdown', text="\n".join(res)) - + self.send_message(chat_id=update.message.chat_id, parse_mode='Markdown', text="\n".join(res)) + def send_message(self, chat_id, parse_mode, text): + try: + self.tbot.sendMessage(chat_id, parse_mode, text) + except telegram.error.NetworkError: + pass + def send_location(self, chat_id, latitude, longitude): + try: + self.tbot.send_location(chat_id, latitude, longitude) + except telegram.error.NetworkError: + pass def _get_player_stats(self): """ Helper method parsing the bot inventory object and returning the player stats object. @@ -91,18 +95,18 @@ def _get_player_stats(self): :rtype: dict """ web_inventory = os.path.join(_base_dir, "web", "inventory-%s.json" % self.bot.config.username) - + 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) + # File may be corrupt. Create a new one. + self.bot.logger.info('[x] Error while opening inventory file for read: %s' % ValueError) json_inventory = [] except: - raise FileIOException("Unexpected error reading from {}".web_inventory) - + raise FileIOException("Unexpected error reading from {}".format(web_inventory)) + return next((x["inventory_item_data"]["player_stats"] for x in json_inventory if x.get("inventory_item_data", {}).get("player_stats", {})), From 2ea7e3e48ea9e7d6e3649b87dc359e2606c78c1f Mon Sep 17 00:00:00 2001 From: DBa2016 Date: Tue, 30 Aug 2016 17:07:23 +0200 Subject: [PATCH 34/38] More config parameters for MoveToMap (#4937) * fixed a runtime error caused by incorrect imports * Moving module-level constants (snipe parameters) into config file --- configs/config.json.map.example | 6 +++ .../cell_workers/move_to_map_pokemon.py | 45 ++++++------------- 2 files changed, 20 insertions(+), 31 deletions(-) diff --git a/configs/config.json.map.example b/configs/config.json.map.example index 7efe794846..050dc332de 100644 --- a/configs/config.json.map.example +++ b/configs/config.json.map.example @@ -201,6 +201,12 @@ "map_path": "raw_data", "walker": "StepWalker", "max_extra_dist_fort": 10, + "skip_rounds": 5, + "update_map_min_distance_meters": 500, + "update_map_min_time_sec": 120, + "snipe_sleep_sec": 2, + "snipe_max_in_chain": 2, + "debug": false, "catch": { "==========Legendaries==========": 0, "Aerodactyl": 1000, diff --git a/pokemongo_bot/cell_workers/move_to_map_pokemon.py b/pokemongo_bot/cell_workers/move_to_map_pokemon.py index 1003f781e0..7c8ba91c71 100644 --- a/pokemongo_bot/cell_workers/move_to_map_pokemon.py +++ b/pokemongo_bot/cell_workers/move_to_map_pokemon.py @@ -70,27 +70,10 @@ from random import uniform from pokemongo_bot.constants import Constants -# Update the map if more than N meters away from the center. (AND'd with -# UPDATE_MAP_MIN_TIME_MINUTES) ULTRABALL_ID = 3 GREATBALL_ID = 2 POKEBALL_ID = 1 -UPDATE_MAP_MIN_DISTANCE_METERS = 500 -# Update the map if it hasn't been updated in n seconds. (AND'd with -# UPDATE_MAP_MIN_DISTANCE_METERS) -UPDATE_MAP_MIN_TIME_SEC = 120 - -# Number of seconds to sleep between teleporting to a snipped Pokemon. -SNIPE_SLEEP_SEC = 2 - -# Max Sniping in chain -SNIPE_MAX_IN_CHAIN = 2 - -# Don't call sniper every time in workers -SNIPE_SKIP_IN_ROUND = 5 - -DEBUG_ON = False class MoveToMapPokemon(BaseTask): @@ -135,11 +118,11 @@ def get_pokemon_from_social(self): pokemon['is_vip'] = pokemon['name'] in self.bot.config.vips if pokemon['name'] not in self.config['catch']: - if DEBUG_ON: + if self.config.get('debug', False): self._emit_failure("Not catching {}".format(pokemon['name'])) continue else: - if DEBUG_ON: + if self.config.get('debug', False): self._emit_log("Catching {}".format(pokemon['name'])) @@ -262,8 +245,8 @@ def update_map_location(self): # update map when 500m away from center and last update longer than 2 minutes away now = int(time.time()) - if (dist > UPDATE_MAP_MIN_DISTANCE_METERS and - now - self.last_map_update > UPDATE_MAP_MIN_TIME_SEC): + if (dist > self.config.get('update_map_min_distance_meters', 500) and + now - self.last_map_update > self.config.get('update_map_min_time_sec', 120)): requests.post( '{}/next_loc?lat={}&lon={}'.format(self.config['address'], self.bot.position[0], @@ -289,10 +272,10 @@ def snipe(self, pokemon): self._teleport_to(pokemon) catch_worker = PokemonCatchWorker(pokemon, self.bot, self.config) api_encounter_response = catch_worker.create_encounter_api_call() - time.sleep(SNIPE_SLEEP_SEC) + time.sleep(self.config.get('snipe_sleep_sec', 2)) self._teleport_back(last_position) self.bot.api.set_position(last_position[0], last_position[1], self.alt, False) - time.sleep(SNIPE_SLEEP_SEC) + time.sleep(self.config.get('snipe_sleep_sec', 2)) self.bot.heartbeat() catch_worker.work(api_encounter_response) self.add_caught(pokemon) @@ -310,7 +293,7 @@ def work(self): ultraballs_quantity = inventory.items().get(ULTRABALL_ID).count if (pokeballs_quantity + superballs_quantity + ultraballs_quantity) < self.min_ball: - if DEBUG_ON: + if self.config.get('debug', False): self._emit_log("Not enough balls to start sniping (have {}, {} needed)".format(pokeballs_quantity + superballs_quantity + ultraballs_quantity, self.min_ball)) return WorkerResult.SUCCESS @@ -318,8 +301,8 @@ def work(self): if self.bot.config.enable_social: if self.config['snipe']: self.by_pass_times = self.by_pass_times + 1 - if self.by_pass_times < SNIPE_SKIP_IN_ROUND: - if DEBUG_ON: + if self.by_pass_times < self.config.get('skip_rounds', 5): + if self.config.get('debug', False): self._emit_log("Skipping pass {}".format(self.by_pass_times)) return WorkerResult.SUCCESS self.by_pass_times = 0 @@ -335,12 +318,12 @@ def work(self): pokemon_list.sort(key=lambda x: x['is_vip'], reverse=True) if len(pokemon_list) < 1: - if DEBUG_ON: + if self.config.get('debug', False): self._emit_log("No pokemons in list to snipe") return WorkerResult.SUCCESS pokemon = pokemon_list[0] - if DEBUG_ON: + if self.config.get('debug', False): self._emit_log('How many pokemon in list: {}'.format(len(pokemon_list))) if self.config['snipe']: if self.snipe_high_prio_only: @@ -349,12 +332,12 @@ def work(self): if self.snipe_high_prio_threshold < pokemon['priority']: self.snipe(pokemon) count = count +1 - if count >= SNIPE_MAX_IN_CHAIN: + if count >= self.config.get('snipe_max_in_chain', 2): return WorkerResult.SUCCESS if count is not 1: - time.sleep(SNIPE_SLEEP_SEC*5) + time.sleep(self.config.get('snipe_sleep_sec', 2)*5) else: - if DEBUG_ON: + if self.config.get('debug', False): self._emit_log('this pokemon is not good enough to snipe {}'.format(pokemon)) return WorkerResult.SUCCESS else: From 8c2d0a5cf38d3cf60ad61040f9cd155de12584f2 Mon Sep 17 00:00:00 2001 From: Julien Lavergne Date: Tue, 30 Aug 2016 23:35:49 +0800 Subject: [PATCH 35/38] Add experimental pokemon upgrade (power-up) logic (#4938) Add upgrade cost data file Add last pokemon level. --- configs/config.json.optimizer.example | 21 +- data/level_to_cpm.json | 3 +- data/pokemon_upgrade_cost.json | 82 +++++++ docs/pokemon_optimizer.md | 208 +++++++++++------- pokemongo_bot/__init__.py | 4 + .../cell_workers/pokemon_optimizer.py | 193 ++++++++++++---- .../event_handlers/colored_logging_handler.py | 1 + tests/inventory_test.py | 2 +- 8 files changed, 382 insertions(+), 132 deletions(-) create mode 100644 data/pokemon_upgrade_cost.json diff --git a/configs/config.json.optimizer.example b/configs/config.json.optimizer.example index 3005ebbd80..c08bed77c2 100644 --- a/configs/config.json.optimizer.example +++ b/configs/config.json.optimizer.example @@ -13,34 +13,39 @@ "evolve_only_with_lucky_egg": false, "evolve_count_for_lucky_egg": 80, "may_use_lucky_egg": true, + "upgrade": true, "groups": { - "gym": ["Dragonite", "Snorlax"] + "gym": ["Dragonite", "Snorlax", "Lapras", "Arcanine"] }, "keep": [ { "mode": "by_family", "top": 1, "sort": [{"iv": 0.9}], - "evolve": true + "evolve": true, + "upgrade": false }, { "mode": "by_family", "top": 1, "sort": [{"ncp": "0.9"}], - "evolve": true + "evolve": true, + "upgrade": false }, { "mode": "by_family", "top": 1, "sort": ["cp"], - "evolve": false + "evolve": false, + "upgrade": false }, { - "mode": "by_pokemon", + "mode": "by_family", "names": ["gym"], - "top": 5, - "sort": ["cp"], - "evolve": false + "top": 3, + "sort": [{"iv": 0.9}, {"ncp": "0.9"}], + "evolve": true, + "upgrade": true } ] } diff --git a/data/level_to_cpm.json b/data/level_to_cpm.json index d2483d9a41..be0d57b3f9 100644 --- a/data/level_to_cpm.json +++ b/data/level_to_cpm.json @@ -77,5 +77,6 @@ "38.5": 0.781790055, "39": 0.78463697, "39.5": 0.787473578, - "40": 0.79030001 + "40": 0.79030001, + "40.5": 0.7931163849 } \ No newline at end of file diff --git a/data/pokemon_upgrade_cost.json b/data/pokemon_upgrade_cost.json new file mode 100644 index 0000000000..06860e57ae --- /dev/null +++ b/data/pokemon_upgrade_cost.json @@ -0,0 +1,82 @@ +[ +[1, 200], +[1, 200], +[1, 200], +[1, 200], +[1, 400], +[1, 400], +[1, 400], +[1, 400], +[1, 600], +[1, 600], +[1, 600], +[1, 600], +[1, 800], +[1, 800], +[1, 800], +[1, 800], +[1, 1000], +[1, 1000], +[1, 1000], +[1, 1000], +[2, 1300], +[2, 1300], +[2, 1300], +[2, 1300], +[2, 1600], +[2, 1600], +[2, 1600], +[2, 1600], +[2, 1900], +[2, 1900], +[2, 1900], +[2, 1900], +[2, 2200], +[2, 2200], +[2, 2200], +[2, 2200], +[2, 2500], +[2, 2500], +[2, 2500], +[2, 2500], +[3, 3000], +[3, 3000], +[3, 3000], +[3, 3000], +[3, 3000], +[3, 3500], +[3, 3500], +[3, 3500], +[4, 4000], +[4, 4000], +[4, 4000], +[4, 4000], +[4, 4500], +[4, 4500], +[4, 4500], +[4, 4500], +[4, 5000], +[4, 5000], +[4, 5000], +[4, 5000], +[6, 6000], +[6, 6000], +[6, 6000], +[6, 6000], +[8, 7000], +[8, 7000], +[8, 7000], +[8, 7000], +[10, 8000], +[10, 8000], +[10, 8000], +[10, 8000], +[12, 9000], +[12, 9000], +[12, 9000], +[12, 9000], +[15, 10000], +[15, 10000], +[15, 10000], +[15, 10000] +] diff --git a/docs/pokemon_optimizer.md b/docs/pokemon_optimizer.md index 4109d90f9b..0752cf1855 100644 --- a/docs/pokemon_optimizer.md +++ b/docs/pokemon_optimizer.md @@ -3,24 +3,25 @@ - [Configuration](#configuration) - [Default configuration](#default-configuration) - [Understand parameters](#understand-parameters) - - [enabled](#enabled) - - [transfer](#transfer) - - [transfer_wait_min](#transfer_wait_min) - - [transfer_wait_max](#transfer_wait_max) - - [evolve](#evolve) - - [evolve_time](#evolve_time) - - [evolve_for_xp](#evolve_for_xp) - - [evolve_only_with_lucky_egg](#evolve_only_with_lucky_egg) - - [evolve_count_for_lucky_egg](#evolve_count_for_lucky_egg) - - [may_use_lucky_egg](#may_use_lucky_egg) - - [groups](#groups) - - [keep](#keep) - - [mode](#keep-mode) - - [names](#keep-names) - - [top](#keep-top) - - [sort](#keep-sort) - - [evolve](#keep-evolve) - - [Examples of configuration](#examples-of-configuration) + - [enabled](#enabled) + - [transfer](#transfer) + - [transfer_wait_min](#transfer_wait_min) + - [transfer_wait_max](#transfer_wait_max) + - [evolve](#evolve) + - [evolve_time](#evolve_time) + - [evolve_for_xp](#evolve_for_xp) + - [evolve_only_with_lucky_egg](#evolve_only_with_lucky_egg) + - [evolve_count_for_lucky_egg](#evolve_count_for_lucky_egg) + - [may_use_lucky_egg](#may_use_lucky_egg) + - [upgrade](#upgrade) + - [groups](#groups) + - [keep](#keep) + - [mode](#keep-mode) + - [names](#keep-names) + - [top](#keep-top) + - [sort](#keep-sort) + - [evolve](#keep-evolve) + - [Examples of configuration](#examples-of-configuration) - [Eevee case](#eevee-case) # About @@ -34,43 +35,58 @@ The Pokemon Optimizer manage transfer and evolution of your Pokemon. ## Default configuration ``` { - "tasks": [ - { - "type": "PokemonOptimizer", - "config": { - "enabled": true, - "transfer": true, - "transfer_wait_min": 3, - "transfer_wait_max": 5, - "evolve": true, - "evolve_time": 25, - "evolve_for_xp": true, - "evolve_only_with_lucky_egg": false, - "evolve_count_for_lucky_egg": 80, - "may_use_lucky_egg": true, - "keep": [ - { - "mode": "by_family", - "top": 1, - "sort": [{"iv": 0.9}], - "evolve": true - }, - { - "mode": "by_family", - "top": 1, - "sort": [{"ncp": "0.9"}], - "evolve": true - }, - { - "mode": "by_family", - "top": 1, - "sort": ["cp"], - "evolve": false - } - ] - } - } - ] + "tasks": [ + { + "type": "PokemonOptimizer", + "config": { + "enabled": true, + "transfer": true, + "transfer_wait_min": 3, + "transfer_wait_max": 5, + "evolve": true, + "evolve_time": 25, + "evolve_for_xp": true, + "evolve_only_with_lucky_egg": false, + "evolve_count_for_lucky_egg": 80, + "may_use_lucky_egg": true, + "upgrade": true, + "groups": { + "gym": ["Dragonite", "Snorlax", "Lapras", "Arcanine"] + }, + "keep": [ + { + "mode": "by_family", + "top": 1, + "sort": [{"iv": 0.9}], + "evolve": true, + "upgrade": false + }, + { + "mode": "by_family", + "top": 1, + "sort": [{"ncp": "0.9"}], + "evolve": true, + "upgrade": false + }, + { + "mode": "by_family", + "top": 1, + "sort": ["cp"], + "evolve": false, + "upgrade": false + }, + { + "mode": "by_family", + "names": ["gym"], + "top": 3, + "sort": [{"iv": 0.9}, {"ncp": "0.9"}], + "evolve": true, + "upgrade": true + } + ] + } + } + ] } ``` @@ -132,6 +148,7 @@ The `evolve` parameter activate or deactivate the evolution of Pokemon. At `true`, you allow the Pokemon Optimizer to evolve every Pokemon that are the best according to your own criteria.
You also allow it to evolve lower quality Pokemon when [`evolve_for_xp`](#evolve_for_xp) parameter is `true`.
At `false`, and regardless of other parameters, no Pokemon is ever going to be evolved. +
`evolve` parameter can be deactivated separately for each rule (see [`evolve`](#keep-evolve)). Note that, whatever is the value you choose to give to that parameter, you will still see logs explaining which Pokemon are evolved.
The purpose of this is to show you what choices are made by the Pokemon Optimizer. @@ -209,6 +226,25 @@ Define whether you allow the Pokemon Optimizer to use a lucky egg before evolvin [[back to top](#pokemon-optimizer)] +### upgrade +| Parameter | Possible values | Default | +|-----------|-----------------|---------| +| `upgrade` | `true`, `false` | `false` | + +The `upgrade` parameter activate or deactivate the upgrade (power-up) of Pokemon. + +At `true`, you allow the Pokemon Optimizer to upgrade every Pokemon that are the best according to your own criteria. +
At `false`, and regardless of other parameters, no Pokemon is ever going to be upgraded. +
`upgrade` parameter can be deactivated separately for each rule (see [`upgrade`](#keep-upgrade)). + +Note that, whatever is the value you choose to give to that parameter, you will still see logs explaining which Pokemon are upgraded. +
The purpose of this is to show you what choices are made by the Pokemon Optimizer. +It can help you rectify your configuration or guide you during manual power-up. + +`Upgraded Magikarp [IV 0.96] [CP 231] [81 candies] [132450 stardust]` + +[[back to top](#pokemon-optimizer)] + ### groups | Parameter | Possible values | Default | |-----------|-----------------|---------| @@ -220,10 +256,10 @@ You can define `groups` of Pokemon to help you restrict rules to a specific set `groups` are list of Pokemon names: ``` "groups": { - "gym": ["Dragonite", "Snorlax"], - "my_love": ["Pikachu"], - "vip": ["Lapras", "Arcanine", "Gyarados", "gym"], - "trash": ["!vip", "!my_love"] + "gym": ["Dragonite", "Snorlax"], + "my_love": ["Pikachu"], + "vip": ["Lapras", "Arcanine", "Gyarados", "gym"], + "trash": ["!vip", "!my_love"] }, ``` @@ -247,9 +283,10 @@ Every Pokemon not selected is candidate for transfer. ``` [ - {"mode": "by_family", "top": 1, "sort": [{"iv": 0.9}], "evolve": True}, - {"mode": "by_family", "top": 1, "sort": [{"ncp": 0.9}], "evolve": True}, - {"mode": "by_family", "top": 1, "sort": ["cp"], "evolve": False} + {"mode": "by_family", "top": 1, "sort": [{"iv": 0.9}], "evolve": True, "upgrade": False}, + {"mode": "by_family", "top": 1, "sort": [{"ncp": 0.9}], "evolve": True, "upgrade": False}, + {"mode": "by_family", "top": 1, "sort": ["cp"], "evolve": False, "upgrade": False}, + {"mode": "by_family", "top": 3, "names": ["gym"], "sort": [{"iv": 0.9}, {"ncp": 0.9}], "evolve": True, "upgrade": True} ] ``` @@ -341,7 +378,7 @@ Define according to which criteria you want to sort your Pokemon. You can put multiple criteria in the list by separating them by a comma: `"sort": ["iv", "cp"]`
If multiple criteria are present, Pokemon will be ranked according to the first, then if equals the second, etc... -In addition to define sorting criteria, the `sort` list may contain minimum requirements for evolution: +In addition to define sorting criteria, the `sort` list may contain minimum requirements for evolution and upgrade: - `"top": 1, "sort": ["iv"], "evolve": True` will rank Pokemon by `iv`.
It will keep and evolve the best of them. - `"top": 1, "sort": [{"iv": 0.9}], "evolve": True` will rank Pokemon by `iv`. @@ -363,6 +400,18 @@ At `true`, all Pokemon selected and meeting the minimum evolution criteria (see [[back to top](#pokemon-optimizer)] +#### keep upgrade +| Parameter | Possible values | Default | +|-----------|-----------------|---------| +| `upgrade` | `true`, `false` | `false` | + +The parameter allow you to selectively control what Pokemon to upgrade. + +At `true`, all Pokemon selected and meeting the minimum upgrade criteria (see [`sort`](#keep-sort)) will be upgraded. +
At `false`, Pokemon selected will not be eligible for upgrade, unless they are also selected by another rule that allow them to upgrade. + +[[back to top](#pokemon-optimizer)] + ## Examples of configuration > Keep the 2 best `iv` of every single Pokemon, and evolve them if they are over 0.9 `iv`. @@ -403,24 +452,24 @@ At `true`, all Pokemon selected and meeting the minimum evolution criteria (see ``` "groups": { - "my loves ones" : ["Dragonite", "Snorlax", "Caterpie"] + "my loves ones" : ["Dragonite", "Snorlax", "Caterpie"] }, "keep": [ - { - "mode": "by_family", - "names": ["my loves ones"] - "top": 1, - "sort": ["iv"], - "evolve": false - }, - { - "mode": "by_family", - "names": ["!my loves ones"] - "top": 1, - "sort": ["iv"], - "evolve": true - } - // + Exclude `my loves ones` from any other rule + { + "mode": "by_family", + "names": ["my loves ones"] + "top": 1, + "sort": ["iv"], + "evolve": false + }, + { + "mode": "by_family", + "names": ["!my loves ones"] + "top": 1, + "sort": ["iv"], + "evolve": true + } + // + Exclude `my loves ones` from any other rule ] ``` @@ -448,5 +497,4 @@ For Eevee Pokemon family, and any other family with multiple paths of evolution, - If you deactivate the global `evolve` parameter, the Pokemon Optimizer will not apply above rules since it considers you are manually controlling the evolution of your Eevees. - [[back to top](#pokemon-optimizer)] diff --git a/pokemongo_bot/__init__.py b/pokemongo_bot/__init__.py index de996f3df3..abba505109 100644 --- a/pokemongo_bot/__init__.py +++ b/pokemongo_bot/__init__.py @@ -437,6 +437,10 @@ def _register_events(self): 'pokemon_evolved', parameters=('pokemon', 'iv', 'cp', 'xp', 'candy') ) + self.event_manager.register_event( + 'pokemon_upgraded', + parameters=('pokemon', 'iv', 'cp', 'candy', 'stardust') + ) self.event_manager.register_event('skip_evolve') self.event_manager.register_event('threw_berry_failed', parameters=('status_code',)) self.event_manager.register_event('vip_pokemon') diff --git a/pokemongo_bot/cell_workers/pokemon_optimizer.py b/pokemongo_bot/cell_workers/pokemon_optimizer.py index 8c7147109e..d2062ba80d 100644 --- a/pokemongo_bot/cell_workers/pokemon_optimizer.py +++ b/pokemongo_bot/cell_workers/pokemon_optimizer.py @@ -1,8 +1,11 @@ import difflib import itertools +import json import math +import os from pokemongo_bot import inventory +from pokemongo_bot.base_dir import _base_dir from pokemongo_bot.base_task import BaseTask from pokemongo_bot.datastore import Datastore from pokemongo_bot.human_behaviour import sleep, action_delay @@ -26,6 +29,13 @@ def initialize(self): self.max_pokemon_storage = inventory.get_pokemon_inventory_size() self.last_pokemon_count = 0 self.pokemon_names = [p.name for p in inventory.pokemons().STATIC_DATA] + self.stardust_count = 0 + self.ongoing_stardust_count = 0 + + pokemon_upgrade_cost_file = os.path.join(_base_dir, "data", "pokemon_upgrade_cost.json") + + with open(pokemon_upgrade_cost_file, "r") as fd: + self.pokemon_upgrade_cost = json.load(fd) self.config_transfer = self.config.get("transfer", False) self.config_transfer_wait_min = self.config.get("transfer_wait_min", 3) @@ -36,10 +46,12 @@ def initialize(self): self.config_evolve_only_with_lucky_egg = self.config.get("evolve_only_with_lucky_egg", False) self.config_evolve_count_for_lucky_egg = self.config.get("evolve_count_for_lucky_egg", 80) self.config_may_use_lucky_egg = self.config.get("may_use_lucky_egg", False) - self.config_groups = self.config.get("groups", {}) - self.config_keep = self.config.get("keep", [{"mode": "by_family", "top": 1, "sort": [{"iv": 0.9}], "evolve": True}, - {"mode": "by_family", "top": 1, "sort": [{"ncp": 0.9}], "evolve": True}, - {"mode": "by_family", "top": 1, "sort": ["cp"], "evolve": False}]) + self.config_upgrade = self.config.get("upgrade", False) + self.config_groups = self.config.get("groups", {"gym": ["Dragonite", "Snorlax", "Lapras", "Arcanine"]}) + self.config_keep = self.config.get("keep", [{"mode": "by_family", "top": 1, "sort": [{"iv": 0.9}], "evolve": True, "upgrade": False}, + {"mode": "by_family", "top": 1, "sort": [{"ncp": 0.9}], "evolve": True, "upgrade": False}, + {"mode": "by_family", "top": 1, "sort": ["cp"], "evolve": False, "upgrade": False}, + {"mode": "by_family", "top": 3, "names": ["gym"], "sort": [{"iv": 0.9}, {"ncp": 0.9}], "evolve": True, "upgrade": True}]) if (not self.config_may_use_lucky_egg) and self.config_evolve_only_with_lucky_egg: self.config_evolve = False @@ -61,6 +73,7 @@ def work(self): self.open_inventory() try_evolve_all = [] + try_upgrade_all = [] keep_all = [] for rule in self.config_keep: @@ -78,8 +91,9 @@ def work(self): if whitelist_names and (name not in whitelist_names): continue - try_evolve, keep = self.get_best_pokemon_for_rule(pokemon_list, rule) + try_evolve, try_upgrade, keep = self.get_best_pokemon_for_rule(pokemon_list, rule) try_evolve_all += try_evolve + try_upgrade_all += try_upgrade keep_all += keep elif mode == "by_family": for family_id, pokemon_list in self.group_by_family_id(inventory.pokemons().all()): @@ -92,11 +106,12 @@ def work(self): continue if family_id == 133: # "Eevee" - try_evolve, keep = self.get_multi_best_pokemon_for_rule(pokemon_list, rule, 3) + try_evolve, try_upgrade, keep = self.get_multi_best_pokemon_for_rule(pokemon_list, rule, 3) else: - try_evolve, keep = self.get_best_pokemon_for_rule(pokemon_list, rule) + try_evolve, try_upgrade, keep = self.get_best_pokemon_for_rule(pokemon_list, rule) try_evolve_all += try_evolve + try_upgrade_all += try_upgrade keep_all += keep elif mode == "overall": pokemon_list = [] @@ -112,28 +127,33 @@ def work(self): pokemon_list.append(pokemon) - try_evolve, keep = self.get_best_pokemon_for_rule(pokemon_list, rule) + try_evolve, try_upgrade, keep = self.get_best_pokemon_for_rule(pokemon_list, rule) try_evolve_all += try_evolve + try_upgrade_all += try_upgrade keep_all += keep try_evolve_all = self.unique_pokemon_list(try_evolve_all) - keep_all = [p for p in self.unique_pokemon_list(keep_all) if p not in try_evolve_all] + try_upgrade_all = self.unique_pokemon_list(try_upgrade_all) + keep_all = self.unique_pokemon_list(keep_all) transfer_all = [] evolve_all = [] + upgrade_all = [] xp_all = [] for family_id, pokemon_list in self.group_by_family_id(inventory.pokemons().all()): try_evolve = [p for p in try_evolve_all if self.get_family_id(p) == family_id] + try_upgrade = [p for p in try_upgrade_all if self.get_family_id(p) == family_id] keep = [p for p in keep_all if self.get_family_id(p) == family_id] - transfer, evolve, xp = self.get_evolution_plan(family_id, pokemon_list, try_evolve, keep) + transfer, evolve, upgrade, xp = self.get_evolution_plan(family_id, pokemon_list, try_evolve, try_upgrade, keep) transfer_all += transfer evolve_all += evolve + upgrade_all += upgrade xp_all += xp - self.apply_optimization(transfer_all, evolve_all, xp_all) + self.apply_optimization(transfer_all, evolve_all, upgrade_all, xp_all) inventory.update_web_inventory() return WorkerResult.SUCCESS @@ -145,6 +165,9 @@ def open_inventory(self): setattr(pokemon, "dps_attack", pokemon.moveset.dps_attack) setattr(pokemon, "dps_defense", pokemon.moveset.dps_defense) + self.stardust_count = self.get_stardust_count() + self.ongoing_stardust_count = self.stardust_count + def get_colorlist_names(self, names): whitelist_names = [] blacklist_names = [] @@ -216,7 +239,7 @@ def get_best_pokemon_for_rule(self, pokemon_list, rule): sorted_pokemon = self.sort_pokemon_list(pokemon_list, rule) if len(sorted_pokemon) == 0: - return ([], []) + return ([], [], []) top = max(rule.get("top", 1), 0) index = int(math.ceil(top)) - 1 @@ -243,55 +266,62 @@ def get_multi_best_pokemon_for_rule(self, family_list, rule, nb_branch): senior_pids = set(p.pokemon_id for p in senior_pokemon_list) try_evolve_all = [] + try_upgrade_all = [] keep_all = [] if not self.config_evolve: # Player handle evolution manually = Fall-back to per Pokemon behavior for _, pokemon_list in self.group_by_pokemon_id(sorted_family): - try_evolve, keep = self.get_best_pokemon_for_rule(pokemon_list, rule) + try_evolve, try_upgrade, keep = self.get_best_pokemon_for_rule(pokemon_list, rule) try_evolve_all += try_evolve + try_upgrade_all += try_upgrade keep_all += keep elif len(senior_pids) < nb_branch: # We did not get every combination yet = All other Pokemon are potentially good to keep for _, pokemon_list in self.group_by_pokemon_id(senior_pokemon_list): - try_evolve, keep = self.get_best_pokemon_for_rule(pokemon_list, rule) + try_evolve, try_upgrade, keep = self.get_best_pokemon_for_rule(pokemon_list, rule) try_evolve_all += try_evolve + try_upgrade_all += try_upgrade keep_all += keep - try_evolve, keep = self.sort_pokemon_list(other_family_list, rule) + try_evolve, try_upgrade, keep = self.get_better_pokemon_for_rule(other_family_list, rule, other_family_list[-1]) try_evolve_all += try_evolve + try_upgrade_all += try_upgrade keep_all += keep else: best = [] for _, pokemon_list in self.group_by_pokemon_id(senior_pokemon_list): - try_evolve, keep = self.get_best_pokemon_for_rule(pokemon_list, rule) + try_evolve, try_upgrade, keep = self.get_best_pokemon_for_rule(pokemon_list, rule) best += try_evolve + best += try_upgrade best += keep worst = self.sort_pokemon_list(best, rule)[-1] - try_evolve_all, keep_all = self.get_better_pokemon_for_rule(sorted_family, rule, worst) + try_evolve_all, try_upgrade_all, keep_all = self.get_better_pokemon_for_rule(sorted_family, rule, worst) - return try_evolve_all, keep_all + return try_evolve_all, try_upgrade_all, keep_all def get_better_pokemon_for_rule(self, pokemon_list, rule, worst): min_score = self.get_score(worst, rule)[0] scored_list = [(p, self.get_score(p, rule)) for p in pokemon_list] - better = [x for x in scored_list if x[1][0] >= min_score] - try_evolve = [x[0] for x in better if x[1][1] is True] - keep = [x[0] for x in better if x[1][1] is False] + best = [x for x in scored_list if x[1][0] >= min_score] + try_evolve = [x[0] for x in best if x[1][1] is True] + try_upgrade = [x[0] for x in best if x[1][1] is False and x[1][2] is True] + keep = [x[0] for x in best] - return try_evolve, keep + return try_evolve, try_upgrade, keep def sort_pokemon_list(self, pokemon_list, rule): return sorted(pokemon_list, key=lambda p: self.get_score(p, rule)[0], reverse=True) def get_score(self, pokemon, rule): score = [] - may_try_evolve = (getattr(pokemon, "has_next_evolution", None) and + may_try_evolve = (getattr(pokemon, "has_next_evolution", False) and pokemon.has_next_evolution() and rule.get("evolve", True)) + may_try_upgrade = rule.get("upgrade", False) for a in rule.get("sort"): if (type(a) is str) or (type(a) is unicode): @@ -301,42 +331,66 @@ def get_score(self, pokemon, rule): value = getattr(pokemon, a.keys()[0], 0) score.append(value) may_try_evolve &= (value >= a.values()[0]) + may_try_upgrade &= (value >= a.values()[0]) elif type(a) is list: value = getattr(pokemon, a[0], 0) score.append(value) may_try_evolve &= (value >= a[1]) + may_try_upgrade &= (value >= a[1]) - return tuple(score), may_try_evolve + return tuple(score), may_try_evolve, may_try_upgrade def unique_pokemon_list(self, pokemon_list): seen = set() return [p for p in pokemon_list if not (p.unique_id in seen or seen.add(p.unique_id))] - def get_evolution_plan(self, family_id, family_list, try_evolve, keep): + def get_evolution_plan(self, family_id, family_list, try_evolve, try_upgrade, keep): candies = inventory.candies().get(family_id).quantity # All the rest is crap, for now crap = list(family_list) - crap = [p for p in crap if p not in try_evolve] crap = [p for p in crap if p not in keep] crap = [p for p in crap if not p.in_fort and not p.is_favorite] - crap.sort(key=lambda p: p.iv * p.ncp, reverse=True) + crap.sort(key=lambda p: (p.iv, p.cp), reverse=True) # We will gain a candy whether we choose to transfer or evolve these Pokemon candies += len(crap) - # Let's see if we can evolve our best Pokemon evolve = [] for pokemon in try_evolve: candies -= pokemon.evolution_cost if candies < 0: - break + continue candies += 1 evolve.append(pokemon) + upgrade = [] + + for pokemon in try_upgrade: + level = int(pokemon.level * 2) - 1 + + if level >= 80: + continue + + full_upgrade_candy_cost = 0 + full_upgrade_stardust_cost = 0 + + for i in range(level, 80): + upgrade_cost = self.pokemon_upgrade_cost[i - 1] + full_upgrade_candy_cost += upgrade_cost[0] + full_upgrade_stardust_cost += upgrade_cost[1] + + candies -= full_upgrade_candy_cost + self.ongoing_stardust_count -= full_upgrade_stardust_cost + + if (candies < 0) or (self.ongoing_stardust_count < 0): + continue + + upgrade.append(pokemon) + if self.config_evolve_for_xp: # Compute how many crap we should keep if we want to batch evolve them for xp lowest_evolution_cost = inventory.pokemons().evolution_cost_for(family_id) @@ -363,46 +417,51 @@ def get_evolution_plan(self, family_id, family_list, try_evolve, keep): xp = [] transfer = crap - return (transfer, evolve, xp) + return (transfer, evolve, upgrade, xp) - def apply_optimization(self, transfer, evolve, xp): + def apply_optimization(self, transfer, evolve, upgrade, xp): self.logger.info("Transferring %s Pokemon", len(transfer)) for pokemon in transfer: self.transfer_pokemon(pokemon) - if len(evolve) + len(xp) == 0: - return + skip_evolve = False if self.config_evolve and self.config_may_use_lucky_egg and (not self.bot.config.test): lucky_egg = inventory.items().get(Item.ITEM_LUCKY_EGG.value) # @UndefinedVariable if lucky_egg.count == 0: if self.config_evolve_only_with_lucky_egg: + skip_evolve = True self.emit_event("skip_evolve", formatted="Skipping evolution step. No lucky egg available") - return elif (len(evolve) + len(xp)) < self.config_evolve_count_for_lucky_egg: if self.config_evolve_only_with_lucky_egg: + skip_evolve = True self.emit_event("skip_evolve", formatted="Skipping evolution step. Not enough Pokemon to evolve with lucky egg: %s/%s" % (len(evolve) + len(xp), self.config_evolve_count_for_lucky_egg)) - return elif self.get_pokemon_slot_left() > 5: + skip_evolve = True self.emit_event("skip_evolve", formatted="Waiting for more Pokemon to evolve with lucky egg: %s/%s" % (len(evolve) + len(xp), self.config_evolve_count_for_lucky_egg)) - return else: self.use_lucky_egg() - self.logger.info("Evolving %s Pokemon (the best)", len(evolve)) + if not skip_evolve: + self.logger.info("Evolving %s Pokemon (the best)", len(evolve)) + + for pokemon in evolve: + self.evolve_pokemon(pokemon) - for pokemon in evolve: - self.evolve_pokemon(pokemon) + self.logger.info("Evolving %s Pokemon (for xp)", len(xp)) - self.logger.info("Evolving %s Pokemon (for xp)", len(xp)) + for pokemon in xp: + self.evolve_pokemon(pokemon) - for pokemon in xp: - self.evolve_pokemon(pokemon) + self.logger.info("Upgrading %s Pokemon [%s stardust]", len(upgrade), self.stardust_count) + + for pokemon in upgrade: + self.upgrade_pokemon(pokemon) def transfer_pokemon(self, pokemon): if self.config_transfer and (not self.bot.config.test): @@ -524,3 +583,53 @@ def evolve_pokemon(self, pokemon): sleep(self.config_evolve_time, 0.1) return True + + def upgrade_pokemon(self, pokemon): + level = int(pokemon.level * 2) - 1 + candy = inventory.candies().get(pokemon.pokemon_id) + + for i in range(level, 80): + upgrade_cost = self.pokemon_upgrade_cost[i - 1] + upgrade_candy_cost = upgrade_cost[0] + upgrade_stardust_cost = upgrade_cost[1] + + if self.config_upgrade and (not self.bot.config.test): + response_dict = self.bot.api.upgrade_pokemon(pokemon_id=pokemon.unique_id) + else: + response_dict = {"responses": {"UPGRADE_POKEMON": {"result": SUCCESS}}} + + if not response_dict: + return False + + result = response_dict.get("responses", {}).get("UPGRADE_POKEMON", {}).get("result", 0) + + if result != SUCCESS: + return False + + upgrade = response_dict.get("responses", {}).get("UPGRADE_POKEMON", {}).get("upgraded_pokemon", {}) + + if self.config_upgrade and (not self.bot.config.test): + candy.consume(upgrade_candy_cost) + self.stardust_count -= upgrade_stardust_cost + + self.emit_event("pokemon_upgraded", + formatted="Upgraded {pokemon} [IV {iv}] [CP {cp}] [{candy} candies] [{stardust} stardust]", + data={"pokemon": pokemon.name, + "iv": pokemon.iv, + "cp": pokemon.cp, + "candy": candy.quantity, + "stardust": self.stardust_count}) + + if self.config_upgrade and (not self.bot.config.test): + inventory.pokemons().remove(pokemon.unique_id) + + new_pokemon = inventory.Pokemon(upgrade) + inventory.pokemons().add(new_pokemon) + + action_delay(self.config_transfer_wait_min, self.config_transfer_wait_max) + + return True + + def get_stardust_count(self): + response_dict = self.bot.api.get_player() + return response_dict.get("responses", {}).get("GET_PLAYER", {}).get("player_data", {}).get("currencies", [{}, {}])[1].get("amount", 0) diff --git a/pokemongo_bot/event_handlers/colored_logging_handler.py b/pokemongo_bot/event_handlers/colored_logging_handler.py index d4a9331f2d..dec6cd3a9c 100644 --- a/pokemongo_bot/event_handlers/colored_logging_handler.py +++ b/pokemongo_bot/event_handlers/colored_logging_handler.py @@ -53,6 +53,7 @@ class ColoredLoggingHandler(EventHandler): 'pokemon_nickname_invalid': 'red', 'pokemon_not_in_range': 'yellow', 'pokemon_release': 'green', + 'pokemon_upgraded': 'green', 'pokemon_vanished': 'red', 'pokestop_empty': 'yellow', 'pokestop_log': 'magenta', diff --git a/tests/inventory_test.py b/tests/inventory_test.py index 4070344ca4..40b3734207 100644 --- a/tests/inventory_test.py +++ b/tests/inventory_test.py @@ -162,7 +162,7 @@ def test_levels_to_cpm(self): max_cpm = l2c.cp_multiplier_for(l2c.MAX_LEVEL) self.assertEqual(l2c.MAX_LEVEL, 40) self.assertEqual(l2c.MAX_CPM, max_cpm) - self.assertEqual(len(l2c.STATIC_DATA), 79) + self.assertEqual(len(l2c.STATIC_DATA), 80) self.assertEqual(l2c.cp_multiplier_for("1"), 0.094) self.assertEqual(l2c.cp_multiplier_for(1), 0.094) From 28450ce9f98122a81a1162b943b7500d3a0f191c Mon Sep 17 00:00:00 2001 From: Simba Zhang Date: Tue, 30 Aug 2016 08:38:16 -0700 Subject: [PATCH 36/38] Set default value of skip_rounds to 30 since many people just use the default value. 30 will behave close to human. --- pokemongo_bot/cell_workers/move_to_map_pokemon.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pokemongo_bot/cell_workers/move_to_map_pokemon.py b/pokemongo_bot/cell_workers/move_to_map_pokemon.py index 7c8ba91c71..65e536dc6b 100644 --- a/pokemongo_bot/cell_workers/move_to_map_pokemon.py +++ b/pokemongo_bot/cell_workers/move_to_map_pokemon.py @@ -301,7 +301,7 @@ def work(self): if self.bot.config.enable_social: if self.config['snipe']: self.by_pass_times = self.by_pass_times + 1 - if self.by_pass_times < self.config.get('skip_rounds', 5): + if self.by_pass_times < self.config.get('skip_rounds', 30): if self.config.get('debug', False): self._emit_log("Skipping pass {}".format(self.by_pass_times)) return WorkerResult.SUCCESS From 8c6a5787fa59a633831c614d4c6525566844f32f Mon Sep 17 00:00:00 2001 From: Matt J Madsen Date: Tue, 30 Aug 2016 11:46:59 -0500 Subject: [PATCH 37/38] Add exception handling for cached forts (#4943) * Add exception handling for cached forts * whitespace fix --- pokemongo_bot/__init__.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/pokemongo_bot/__init__.py b/pokemongo_bot/__init__.py index abba505109..fb288cfc37 100644 --- a/pokemongo_bot/__init__.py +++ b/pokemongo_bot/__init__.py @@ -1317,8 +1317,15 @@ def _load_recent_forts(self): cached_forts_path = os.path.join(_base_dir, 'data', 'recent-forts-%s.json' % self.config.username) try: # load the cached recent forts - with open(cached_forts_path) as f: - cached_recent_forts = json.load(f) + cached_recent_forts = [] + try: + with open(cached_forts_path) as f: + cached_recent_forts = json.load(f) + except (IOError, ValueError) as e: + self.bot.logger.info('[x] Error while opening cached forts: %s' % e, 'red') + pass + except: + raise FileIOException("Unexpected error opening {}".cached_forts_path) num_cached_recent_forts = len(cached_recent_forts) num_recent_forts = len(self.recent_forts) From 1d9c94c1b75f43d50cdd6f87959701b0f9b1aee2 Mon Sep 17 00:00:00 2001 From: Simba Zhang Date: Tue, 30 Aug 2016 10:53:55 -0700 Subject: [PATCH 38/38] update web repo to have better web ui contribute. --- .gitmodules | 2 +- web | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitmodules b/.gitmodules index 612529e7ad..1d5fef9215 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "web"] path = web - url = https://github.com/OpenPoGo/OpenPoGoWeb.git \ No newline at end of file + url = https://github.com/PokemonGoF/PokemonGo-Web diff --git a/web b/web index 6ba5609c61..e9322cd895 160000 --- a/web +++ b/web @@ -1 +1 @@ -Subproject commit 6ba5609c6151507b5b832a74e471b6b7b1a182c9 +Subproject commit e9322cd8952bcc1bbd70e302d3943d0abe5d3a00