diff --git a/.gitignore b/.gitignore index 550648c7b6..0f969ba7cc 100644 --- a/.gitignore +++ b/.gitignore @@ -106,6 +106,7 @@ out/ # Personal load details src/ web/ +data/*.db data/last-location*.json data/cells-*.json data/map-caught-*.json diff --git a/.travis.yml b/.travis.yml index aef60fa3c7..0aab748960 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,4 +12,5 @@ install: - pip install pylint script: - python pylint-recursive.py + - python json-validate.py configs/*.json.* - python -m unittest discover -v -p "*_test.py" diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 33aee29d31..c307c84537 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -70,3 +70,5 @@ * Quantra * pmquan * net8q + * SyncX + * umbreon222 diff --git a/configs/config.json.cluster.example b/configs/config.json.cluster.example index 5c90d8f4b2..ae3c3ad857 100644 --- a/configs/config.json.cluster.example +++ b/configs/config.json.cluster.example @@ -5,6 +5,8 @@ "location": "SOME_LOCATION", "gmapkey": "GOOGLE_MAPS_API_KEY", "encrypt_location": "", + "websocket_server": false, + "heartbeat_threshold": 10, "tasks": [ { "type": "HandleSoftBan" @@ -39,7 +41,18 @@ } }, { - "type": "TransferPokemon" + "type": "TransferPokemon", + "config": { + "transfer_wait_min": 1, + "transfer_wait_max": 4 + } + }, + { + "type": "NicknamePokemon", + "config": { + "enabled": false, + "nickname_template": "{iv_pct}_{iv_ads}" + } }, { "type": "EvolvePokemon", @@ -57,6 +70,10 @@ "type": "RecycleItems", "config": { "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 }, @@ -64,17 +81,42 @@ "Hyper Potion": { "keep" : 30 }, "Revive": { "keep" : 30 }, "Razz Berry": { "keep" : 100 } - } + }, + "recycle_wait_min": 1, + "recycle_wait_max": 4 } }, + { + "type": "CatchPokemon", + "config": { + "catch_visible_pokemon": true, + "catch_lured_pokemon": true, + "min_ultraball_to_keep": 5, + "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": 2, + "catch_wait_max": 6, + "berry_wait_min": 2, + "berry_wait_max": 3, + "changeball_wait_min": 2, + "changeball_wait_max": 3 + } + } + }, { - "type": "CatchVisiblePokemon" - }, - { - "type": "CatchLuredPokemon" - }, - { - "type": "SpinFort" + "type": "SpinFort", + "config": { + "spin_wait_min": 2, + "spin_wait_max": 3 + } }, { "type": "FollowCluster", @@ -86,22 +128,17 @@ ], "forts": { "avoid_circles": true, - "max_circle_size": 50 + "max_circle_size": 50, + "cache_recent_forts": true }, - "websocket_server": false, "walk_max": 4.16, "walk_min": 2.16, - "action_wait_min": 1, - "action_wait_max": 4, "debug": false, "test": false, "health_record": true, "location_cache": true, "distance_unit": "km", "reconnecting_timeout": 15, - "catch_randomize_reticle_factor": 1.0, - "catch_randomize_spin_factor": 1.0, - "min_ultraball_to_keep": 10, "logging_color": true, "catch": { "any": {"catch_above_cp": 0, "catch_above_iv": 0, "logic": "or"}, diff --git a/configs/config.json.example b/configs/config.json.example index d5dfebf0c5..3e51a45b4a 100644 --- a/configs/config.json.example +++ b/configs/config.json.example @@ -1,175 +1,191 @@ -{ - "auth_service": "google", - "username": "YOUR_USERNAME", - "password": "YOUR_PASSWORD", - "location": "SOME_LOCATION", - "gmapkey": "GOOGLE_MAPS_API_KEY", - "encrypt_location": "", - "websocket_server": false, - "heartbeat_threshold": 10, - "tasks": [ - { - "type": "HandleSoftBan" - }, - { - "type": "SleepSchedule", - "config": { - "enabled": false, - "time": "22:54", - "duration":"7:46", - "time_random_offset": "00:24", - "duration_random_offset": "00:43" - } - }, - { - "type": "CollectLevelUpReward" - }, - { - "type": "IncubateEggs", - "config": { - "longer_eggs_first": true - } - }, - { - "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": "TransferPokemon" - }, - { - "type": "NicknamePokemon", - "config": { - "enabled": false, - "nickname_template": "{iv_pct}_{iv_ads}" - } - }, - { - "type": "EvolvePokemon", - "config": { - "evolve_all": "none", - "first_evolve_by": "cp", - "evolve_above_cp": 500, - "evolve_above_iv": 0.8, - "logic": "or", - "evolve_speed": 20, - "use_lucky_egg": false - } - }, - { - "type": "RecycleItems", - "config": { - "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 } - } - } - }, - { - "type": "CatchVisiblePokemon" - }, - { - "type": "CatchLuredPokemon" - }, - { - "type": "SpinFort" - }, - { - "type": "MoveToFort", - "config": { - "lure_attraction": true, - "lure_max_distance": 2000 - } - }, - { - "type": "FollowSpiral", - "config": { - "diameter": 4, - "step_size": 70 - } - } - ], - "map_object_cache_time": 5, - "forts": { - "avoid_circles": true, - "max_circle_size": 50, - "cache_recent_forts": true - }, - "walk_max": 4.16, - "walk_min": 2.16, - "action_wait_min": 1, - "action_wait_max": 4, - "debug": false, - "test": false, - "health_record": true, - "location_cache": true, - "distance_unit": "km", - "reconnecting_timeout": 15, - "catch_randomize_reticle_factor": 1.0, - "catch_randomize_spin_factor": 1.0, - "min_ultraball_to_keep": 10, - "logging_color": true, - "catch": { - "any": {"catch_above_cp": 0, "catch_above_iv": 0, "logic": "or"}, - "// Example of always catching Rattata:": {}, - "// Rattata": { "always_catch" : true } - }, - "catch_throw_parameters": { - "excellent_rate": 0.1, - "great_rate": 0.5, - "nice_rate": 0.3, - "normal_rate": 0.1, - "spin_success_rate" : 0.6 - }, - "release": { - "any": {"release_below_cp": 0, "release_below_iv": 0, "logic": "or"}, - "// Example of always releasing Rattata:": {}, - "// Rattata": {"always_release": true}, - "// Example of keeping 3 stronger (based on CP) Pidgey:": {}, - "// Pidgey": {"keep_best_cp": 3}, - "// Example of keeping 2 stronger (based on IV) Zubat:": {}, - "// Zubat": {"keep_best_iv": 2}, - "// Also, it is working with any": {}, - "// any": {"keep_best_iv": 3}, - "// Example of keeping the 2 strongest (based on CP) and 3 best (based on IV) Zubat:": {}, - "// Zubat": {"keep_best_cp": 2, "keep_best_iv": 3} - }, - "vips" : { - "Any pokemon put here directly force to use Berry & Best Ball to capture, to secure the capture rate!": {}, - "any": {"catch_above_cp": 1200, "catch_above_iv": 0.9, "logic": "or" }, - "Lapras": {}, - "Moltres": {}, - "Zapdos": {}, - "Articuno": {}, - - "// S-Tier pokemons (if pokemon can be evolved into tier, list the representative)": {}, - "Mewtwo": {}, - "Dragonite": {}, - "Snorlax": {}, - "// Mew evolves to Mewtwo": {}, - "Mew": {}, - "Arcanine": {}, - "Vaporeon": {}, - "Gyarados": {}, - "Exeggutor": {}, - "Muk": {}, - "Weezing": {}, - "Flareon": {} - - } -} +{ + "auth_service": "google", + "username": "YOUR_USERNAME", + "password": "YOUR_PASSWORD", + "location": "SOME_LOCATION", + "gmapkey": "GOOGLE_MAPS_API_KEY", + "encrypt_location": "", + "websocket_server": false, + "heartbeat_threshold": 10, + "tasks": [ + { + "type": "HandleSoftBan" + }, + { + "type": "SleepSchedule", + "config": { + "enabled": false, + "time": "22:54", + "duration":"7:46", + "time_random_offset": "00:24", + "duration_random_offset": "00:43" + } + }, + { + "type": "CollectLevelUpReward" + }, + { + "type": "IncubateEggs", + "config": { + "longer_eggs_first": true + } + }, + { + "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": "TransferPokemon", + "config": { + "transfer_wait_min": 1, + "transfer_wait_max": 4 + } + }, + { + "type": "NicknamePokemon", + "config": { + "enabled": false, + "nickname_template": "{iv_pct}_{iv_ads}" + } + }, + { + "type": "EvolvePokemon", + "config": { + "evolve_all": "none", + "first_evolve_by": "cp", + "evolve_above_cp": 500, + "evolve_above_iv": 0.8, + "logic": "or", + "evolve_speed": 20, + "use_lucky_egg": false + } + }, + { + "type": "RecycleItems", + "config": { + "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": 1, + "recycle_wait_max": 4 + } + }, + { + "type": "CatchPokemon", + "config": { + "catch_visible_pokemon": true, + "catch_lured_pokemon": true, + "min_ultraball_to_keep": 5, + "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": 2, + "catch_wait_max": 6, + "berry_wait_min": 2, + "berry_wait_max": 3, + "changeball_wait_min": 2, + "changeball_wait_max": 3 + } + } + }, + { + "type": "SpinFort", + "config": { + "spin_wait_min": 2, + "spin_wait_max": 3 + } + }, + { + "type": "MoveToFort", + "config": { + "lure_attraction": true, + "lure_max_distance": 2000 + } + }, + { + "type": "FollowSpiral", + "config": { + "diameter": 4, + "step_size": 70 + } + } + ], + "map_object_cache_time": 5, + "forts": { + "avoid_circles": true, + "max_circle_size": 50, + "cache_recent_forts": true + }, + "walk_max": 4.16, + "walk_min": 2.16, + "debug": false, + "test": false, + "health_record": true, + "location_cache": true, + "distance_unit": "km", + "reconnecting_timeout": 15, + "logging_color": true, + "catch": { + "any": {"catch_above_cp": 0, "catch_above_iv": 0, "logic": "or"}, + "// Example of always catching Rattata:": {}, + "// Rattata": { "always_catch" : true } + }, + "release": { + "any": {"release_below_cp": 0, "release_below_iv": 0, "logic": "or"}, + "// Example of always releasing Rattata:": {}, + "// Rattata": {"always_release": true}, + "// Example of keeping 3 stronger (based on CP) Pidgey:": {}, + "// Pidgey": {"keep_best_cp": 3}, + "// Example of keeping 2 stronger (based on IV) Zubat:": {}, + "// Zubat": {"keep_best_iv": 2}, + "// Also, it is working with any": {}, + "// any": {"keep_best_iv": 3}, + "// Example of keeping the 2 strongest (based on CP) and 3 best (based on IV) Zubat:": {}, + "// Zubat": {"keep_best_cp": 2, "keep_best_iv": 3} + }, + "vips" : { + "Any pokemon put here directly force to use Berry & Best Ball to capture, to secure the capture rate!": {}, + "any": {"catch_above_cp": 1200, "catch_above_iv": 0.9, "logic": "or" }, + "Lapras": {}, + "Moltres": {}, + "Zapdos": {}, + "Articuno": {}, + + "// S-Tier pokemons (if pokemon can be evolved into tier, list the representative)": {}, + "Mewtwo": {}, + "Dragonite": {}, + "Snorlax": {}, + "// Mew evolves to Mewtwo": {}, + "Mew": {}, + "Arcanine": {}, + "Vaporeon": {}, + "Gyarados": {}, + "Exeggutor": {}, + "Muk": {}, + "Weezing": {}, + "Flareon": {} + } +} diff --git a/configs/config.json.map.example b/configs/config.json.map.example index d691011e0f..ba0716eefd 100644 --- a/configs/config.json.map.example +++ b/configs/config.json.map.example @@ -5,6 +5,7 @@ "location": "SOME_LOCATION", "gmapkey": "GOOGLE_MAPS_API_KEY", "encrypt_location": "", + "websocket_server": false, "heartbeat_threshold": 10, "tasks": [ { @@ -40,21 +41,39 @@ } }, { - "type": "TransferPokemon" + "type": "TransferPokemon", + "config": { + "transfer_wait_min": 1, + "transfer_wait_max": 4 + } + }, + { + "type": "NicknamePokemon", + "config": { + "enabled": false, + "nickname_template": "{iv_pct}_{iv_ads}" + } }, { "type": "EvolvePokemon", "config": { - "evolve_all": "NONE", - "evolve_cp_min": 300, - "evolve_speed": 20, - "use_lucky_egg": false + "evolve_all": "none", + "first_evolve_by": "cp", + "evolve_above_cp": 500, + "evolve_above_iv": 0.8, + "logic": "or", + "evolve_speed": 20, + "use_lucky_egg": false } }, { "type": "RecycleItems", "config": { "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 }, @@ -62,24 +81,48 @@ "Hyper Potion": { "keep" : 30 }, "Revive": { "keep" : 30 }, "Razz Berry": { "keep" : 100 } - } + }, + "recycle_wait_min": 1, + "recycle_wait_max": 4 } }, + { + "type": "CatchPokemon", + "config": { + "catch_visible_pokemon": true, + "catch_lured_pokemon": true, + "min_ultraball_to_keep": 5, + "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": 2, + "catch_wait_max": 6, + "berry_wait_min": 2, + "berry_wait_max": 3, + "changeball_wait_min": 2, + "changeball_wait_max": 3 + } + } + }, { - "type": "CatchVisiblePokemon" - }, - { - "type": "CatchLuredPokemon" - }, - { - "type": "SpinFort" + "type": "SpinFort", + "config": { + "spin_wait_min": 2, + "spin_wait_max": 3 + } }, { "type": "MoveToMapPokemon", "config": { "address": "http://localhost:5000", "max_distance": 500, - "min_time": 60, "min_ball": 50, "prioritize_vips": true, "snipe": true, @@ -320,31 +363,34 @@ } }, { - "type": "MoveToFort" + "type": "MoveToFort", + "config": { + "lure_attraction": true, + "lure_max_distance": 2000 + } }, { - "type": "FollowSpiral" + "type": "FollowSpiral", + "config": { + "diameter": 4, + "step_size": 70 + } } ], "map_object_cache_time": 5, "forts": { "avoid_circles": true, - "max_circle_size": 50 + "max_circle_size": 50, + "cache_recent_forts": true }, - "websocket_server": false, "walk_max": 4.16, "walk_min": 2.16, - "action_wait_min": 1, - "action_wait_max": 4, "debug": false, "test": false, "health_record": true, "location_cache": true, "distance_unit": "km", "reconnecting_timeout": 15, - "catch_randomize_reticle_factor": 1.0, - "catch_randomize_spin_factor": 1.0, - "min_ultraball_to_keep": 10, "logging_color": true, "catch": { "any": {"catch_above_cp": 0, "catch_above_iv": 0, "logic": "or"}, diff --git a/configs/config.json.optimizer.example b/configs/config.json.optimizer.example index dbd898bc14..1dd826428f 100644 --- a/configs/config.json.optimizer.example +++ b/configs/config.json.optimizer.example @@ -5,19 +5,41 @@ "location": "SOME_LOCATION", "gmapkey": "GOOGLE_MAPS_API_KEY", "encrypt_location": "", + "websocket_server": false, + "heartbeat_threshold": 10, "tasks": [ - { - "type": "HandleSoftBan" - }, - { - "type": "CollectLevelUpReward" - }, - { - "type": "IncubateEggs", - "config": { - "longer_eggs_first": true - } - }, + { + "type": "HandleSoftBan" + }, + { + "type": "SleepSchedule", + "config": { + "enabled": false, + "time": "22:54", + "duration":"7:46", + "time_random_offset": "00:24", + "duration_random_offset": "00:43" + } + }, + { + "type": "CollectLevelUpReward" + }, + { + "type": "IncubateEggs", + "config": { + "longer_eggs_first": true + } + }, + { + "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": "PokemonOptimizer", "config": { @@ -26,28 +48,34 @@ "// 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": 1, + "transfer_wait_max": 4, "// 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, - "// the 'use_candies_for_xp' parameter let you choose if you want the optimizer": {}, + "// 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": {}, - "use_candies_for_xp": true, - "// the '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.": {}, - "use_lucky_egg": true, + "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": true, - "// the 'minimum_evolve_for_lucky_egg' parameter let you define the minimum": {}, + "// 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": {}, - "minimum_evolve_for_lucky_egg": 90, + "evolve_count_for_lucky_egg": 90, + "// 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,": {}, @@ -95,59 +123,88 @@ } ] } - }, - { - "type": "RecycleItems", - "config": { - "min_empty_space": 15, - "item_filter": { - "Pokeball": { "keep": 100 }, - "Potion": { "keep": 10 }, - "Super Potion": { "keep": 20 }, - "Hyper Potion": { "keep": 30 }, - "Revive": { "keep": 30 }, - "Razz Berry": { "keep": 100 } - } - } - }, - { - "type": "CatchVisiblePokemon" - }, - { - "type": "CatchLuredPokemon" - }, + }, + { + "type": "RecycleItems", + "config": { + "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": 1, + "recycle_wait_max": 4 + } + }, { - "type": "SpinFort", - "config": { - "ignore_item_count": true - } + "type": "CatchPokemon", + "config": { + "catch_visible_pokemon": true, + "catch_lured_pokemon": true, + "min_ultraball_to_keep": 5, + "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": 2, + "catch_wait_max": 6, + "berry_wait_min": 2, + "berry_wait_max": 3, + "changeball_wait_min": 2, + "changeball_wait_max": 3 + } + } }, - { - "type": "MoveToFort", - "config": { - "lure_attraction": true, - "lure_max_distance": 2000, - "ignore_item_count": true - } + { + "type": "SpinFort", + "config": { + "spin_wait_min": 2, + "spin_wait_max": 3 + } + }, + { + "type": "MoveToFort", + "config": { + "lure_attraction": true, + "lure_max_distance": 2000 + } + }, + { + "type": "FollowSpiral", + "config": { + "diameter": 4, + "step_size": 70 } + } ], "map_object_cache_time": 5, "forts": { - "avoid_circles": true, - "max_circle_size": 50 + "avoid_circles": true, + "max_circle_size": 50, + "cache_recent_forts": true }, - "websocket_server": true, "walk_max": 4.16, "walk_min": 2.16, - "action_wait_min": 1, - "action_wait_max": 4, "debug": false, "test": false, - "health_record": false, + "health_record": true, "location_cache": true, "distance_unit": "km", "reconnecting_timeout": 15, - "min_ultraball_to_keep": 10, "logging_color": true, "catch": { "any": { diff --git a/configs/config.json.path.example b/configs/config.json.path.example index e8edf47a84..9f8f10cbde 100644 --- a/configs/config.json.path.example +++ b/configs/config.json.path.example @@ -5,6 +5,8 @@ "location": "SOME_LOCATION", "gmapkey": "GOOGLE_MAPS_API_KEY", "encrypt_location": "", + "websocket_server": false, + "heartbeat_threshold": 10, "tasks": [ { "type": "HandleSoftBan" @@ -33,13 +35,24 @@ "config": { "enabled": false, "min_interval": 10, - "stats": ["uptime", "stardust_earned", "xp_earned", "xp_per_hour", "stops_visited"], + "stats": ["username", "uptime", "stardust_earned", "xp_earned", "xp_per_hour", "stops_visited"], "terminal_log": true, "terminal_title": true } }, { - "type": "TransferPokemon" + "type": "TransferPokemon", + "config": { + "transfer_wait_min": 1, + "transfer_wait_max": 4 + } + }, + { + "type": "NicknamePokemon", + "config": { + "enabled": false, + "nickname_template": "{iv_pct}_{iv_ads}" + } }, { "type": "EvolvePokemon", @@ -57,6 +70,10 @@ "type": "RecycleItems", "config": { "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 }, @@ -64,17 +81,42 @@ "Hyper Potion": { "keep" : 30 }, "Revive": { "keep" : 30 }, "Razz Berry": { "keep" : 100 } - } + }, + "recycle_wait_min": 1, + "recycle_wait_max": 4 } }, { - "type": "CatchVisiblePokemon" - }, - { - "type": "CatchLuredPokemon" + "type": "CatchPokemon", + "config": { + "catch_visible_pokemon": true, + "catch_lured_pokemon": true, + "min_ultraball_to_keep": 5, + "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": 2, + "catch_wait_max": 6, + "berry_wait_min": 2, + "berry_wait_max": 3, + "changeball_wait_min": 2, + "changeball_wait_max": 3 + } + } }, { - "type": "SpinFort" + "type": "SpinFort", + "config": { + "spin_wait_min": 2, + "spin_wait_max": 3 + } }, { "type": "FollowPath", @@ -90,20 +132,14 @@ "avoid_circles": true, "max_circle_size": 50 }, - "websocket_server": false, "walk_max": 4.16, "walk_min": 2.16, - "action_wait_min": 1, - "action_wait_max": 4, "debug": false, "test": false, "health_record": true, "location_cache": true, "distance_unit": "km", "reconnecting_timeout": 15, - "catch_randomize_reticle_factor": 1.0, - "catch_randomize_spin_factor": 1.0, - "min_ultraball_to_keep": 10, "logging_color": true, "catch": { "any": {"catch_above_cp": 0, "catch_above_iv": 0, "logic": "or"}, diff --git a/configs/config.json.pokemon.example b/configs/config.json.pokemon.example index 1876ba82b2..56eaf60b6a 100644 --- a/configs/config.json.pokemon.example +++ b/configs/config.json.pokemon.example @@ -5,6 +5,7 @@ "location": "SOME_LOCATION", "gmapkey": "GOOGLE_MAPS_API_KEY", "encrypt_location": "", + "websocket_server": false, "heartbeat_threshold": 10, "tasks": [ { @@ -40,7 +41,18 @@ } }, { - "type": "TransferPokemon" + "type": "TransferPokemon", + "config": { + "transfer_wait_min": 1, + "transfer_wait_max": 4 + } + }, + { + "type": "NicknamePokemon", + "config": { + "enabled": false, + "nickname_template": "{iv_pct}_{iv_ads}" + } }, { "type": "EvolvePokemon", @@ -58,6 +70,10 @@ "type": "RecycleItems", "config": { "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 }, @@ -65,17 +81,42 @@ "Hyper Potion": { "keep" : 30 }, "Revive": { "keep" : 30 }, "Razz Berry": { "keep" : 100 } - } + }, + "recycle_wait_min": 1, + "recycle_wait_max": 4 } }, + { + "type": "CatchPokemon", + "config": { + "catch_visible_pokemon": true, + "catch_lured_pokemon": true, + "min_ultraball_to_keep": 5, + "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": 2, + "catch_wait_max": 6, + "berry_wait_min": 2, + "berry_wait_max": 3, + "changeball_wait_min": 2, + "changeball_wait_max": 3 + } + } + }, { - "type": "CatchVisiblePokemon" - }, - { - "type": "CatchLuredPokemon" - }, - { - "type": "SpinFort" + "type": "SpinFort", + "config": { + "spin_wait_min": 2, + "spin_wait_max": 3 + } }, { "type": "MoveToFort", @@ -95,22 +136,17 @@ "map_object_cache_time": 5, "forts": { "avoid_circles": true, - "max_circle_size": 50 + "max_circle_size": 50, + "cache_recent_forts": true }, - "websocket_server": false, "walk_max": 4.16, "walk_min": 2.16, - "action_wait_min": 1, - "action_wait_max": 4, "debug": false, "test": false, "health_record": true, "location_cache": true, "distance_unit": "km", "reconnecting_timeout": 15, - "catch_randomize_reticle_factor": 1.0, - "catch_randomize_spin_factor": 1.0, - "min_ultraball_to_keep": 10, "logging_color": true, "catch": { "any": {"catch_above_cp": 0, "catch_above_iv": 0, "logic": "or" }, diff --git a/docker-compose_tor.yml b/docker-compose_tor.yml new file mode 100644 index 0000000000..e2f664f92c --- /dev/null +++ b/docker-compose_tor.yml @@ -0,0 +1,38 @@ +version: '2' +services: + torproxy: + image: jess/tor-proxy + expose: + - "9050" + restart: "always" + privoxy: + image: jess/privoxy + links: + - torproxy + expose: + - "8118" + restart: "always" + bot1-pokego: + build: . + links: + - privoxy + environment: + - HTTPS_PROXY=privoxy:8118 + - HTTP_PROXY=privoxy:8118 + volumes: + - ./configs:/usr/src/app/configs + - ./web:/usr/src/app/web + stdin_open: true + tty: true + bot1-pokegoweb: + image: python:2.7 + ports: + - "8000:8000" + volumes_from: + - bot1-pokego + volumes: + - ./configs:/usr/src/app/web/config + working_dir: /usr/src/app/web + command: bash -c "echo 'Serving HTTP on 0.0.0.0 port 8000' && python -m SimpleHTTPServer > /dev/null 2>&1" + depends_on: + - bot1-pokego diff --git a/docs/configuration_files.md b/docs/configuration_files.md index 0e6a4b9803..706904e44d 100644 --- a/docs/configuration_files.md +++ b/docs/configuration_files.md @@ -24,8 +24,7 @@ The behaviors of the bot are configured via the `tasks` key in the `config.json`. This enables you to list what you want the bot to do and change the priority of those tasks by reordering them in the list. This list of tasks is run repeatedly and in order. For more information on why we are moving config to this format, check out the [original proposal](https://github.com/PokemonGoF/PokemonGo-Bot/issues/142). ### Task Options: -* CatchLuredPokemon -* CatchVisiblePokemon +* CatchPokemon * 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" * `evolve_speed`: Default `20` @@ -69,10 +68,7 @@ The following configuration tells the bot to transfer all the Pokemon that match "type": "RecycleItems" }, { - "type": "CatchVisiblePokemon" - }, - { - "type": "CatchLuredPokemon" + "type": "CatchPokemon" }, { "type": "SpinFort" @@ -290,6 +286,58 @@ Key | Info } ``` +## CatchPokemon `catch_simulation` Settings + +These settings determine how the bot will simulate the app by adding pauses to throw the ball and navigate menus. All times are in seconds. To configure these settings add them to the config in the CatchPokemon task. + +### Default Settings +The default settings are 'safe' settings intended to simulate human and app behaviour. + +``` +"catch_simulation": { + "flee_count": 3, + "flee_duration": 2, + "catch_wait_min": 2, + "catch_wait_max": 6, + "berry_wait_min": 2, + "berry_wait_max": 3, + "changeball_wait_min": 2, + "changeball_wait_max": 3 +} +``` + +### Settings Description + +Setting | Description +---- | ---- +`flee_count` | The maximum number of times catching animation will play before the pokemon breaks free +`flee_duration` | The length of time for each animation +`catch_wait_min`| The minimum amount of time to throw the ball +`catch_wait_max`| The maximum amount of time to throw the ball +`berry_wait_min`| The minimum amount of time to use a berry +`berry_wait_max`| The maximum amount of time to use a berry +`changeball_wait_min`| The minimum amount of time to change ball +`changeball_wait_max`| The maximum amount of time to change ball + +### `flee_count` and `flee_duration` +This part is app simulation and the default settings are advised. When we hit a pokemon in the app the animation will play randomly 1, 2 or 3 times for roughly 2 seconds each time. So we pause for a random number of animations up to `flee_count` of duration `flee_duration` + +### Previous Behaviour +If you want to make your bot behave as it did prior to this update please use the following settings. + +``` +"catch_simulation": { + "flee_count": 1, + "flee_duration": 2, + "catch_wait_min": 0, + "catch_wait_max": 0, + "berry_wait_min": 0, + "berry_wait_max": 0, + "changeball_wait_min": 0, + "changeball_wait_max": 0 +} +``` + ## Sniping _(MoveToLocation)_ ### Description diff --git a/docs/installation.md b/docs/installation.md index ebdcc9ba01..36f34bfb88 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -33,27 +33,18 @@ We do recommend Windows users to use [Docker](#docker) this will work much easie ##Requirements -- [Python 2.7.x](http://docs.python-guide.org/en/latest/starting/installation/) -- [git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) -- [Protoc](https://github.com/google/protobuf/releases/download/v3.0.0-beta-4/protoc-3.0.0-beta-4-win32.zip) -- [Microsoft Visual C++ Compiler for Python 2.7](http://www.microsoft.com/en-us/download/details.aspx?id=44266) - ###Easy Installation -1. Download `PokemonGo-Bot-Install.bat` file from [HERE](https://raw.githubusercontent.com/nivong/PokemonGo-Bot/dev/windows_bat/PokemonGo-Bot-Install.bat) -2. Run `PokemonGo-Bot-install.bat` -After that has done the bot will be installed -3. Run `PokemonGo-Bot-Start.bat` -This will start the bot and the web interface +1. Download [PokemonGo-Bot-Install.bat](https://github.com/PokemonGoF/PokemonGo-Bot/blob/master/windows_bat/PokemonGo-Bot-Install.bat) +2. Download `encrypt.so` and `encrypt.dll` or `encrypt_64.dll` to the same folder of the `PokemonGo-Bot-Install.bat`. +3. Run `PokemonGo-Bot-install.bat`. +After that has been done the bot will be installed. +4. Run `PokemonGo-Bot-Start.bat`. +This will start the bot and the web interface. ### To update the bot 3. Run `PokemonGo-Bot-Start.bat` This will check for an update and will start the bot afterwards. -### To repair the bot if it isn't working for some reason -1. Stop the bot by closing everything -2. Run `PokemonGo-Bot-Repair.bat` -3. Rerun the bot by using `PokemonGo-Bot-StartBot.bat` - #Docker ###Easy installation @@ -69,7 +60,7 @@ cd PokemonGo-Bot docker build --build-arg timezone=Europe/London -t pokemongo-bot . ``` -Optionally you can set your timezone with the --build-arg option (default is Etc/UTC) +Optionally you can set your timezone with the --build-arg option (default is Etc/UTC). You can find an exhaustive list of timezone here: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones After build process you can verify that the image was created with: @@ -77,7 +68,7 @@ After build process you can verify that the image was created with: docker images ``` -To run PokemonGo-Bot Docker image you've created: +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 @@ -90,6 +81,51 @@ docker run --name=bot1-pokegoweb --rm -it --volumes-from bot1-pokego -p 8000:800 ``` The OpenPoGoWeb will be served on `http://:8000` +###Remarks for Windows + +Even if the previous command are valid, you will not be able to visualize the web view under Windows. +To visualize the web view, execute instead the following commands (*make sure you are in the root folder and that your docker images is built*): + +- Run the bot container: + +``` +docker run --name=bot1-pokego --rm -it -v $(pwd)/configs/config.json:/usr/src/app/configs/config.json -v $(pwd)/web/:/usr/src/app/web/ pokemongo-bot +``` + +- Run the web container: + +``` +docker run --name=bot1-pokegoweb --rm -it --volumes-from bot1-pokego -p 8000:8000 -v $(pwd)/configs/userdata.js:/usr/src/app/web/userdata.js -w /usr/src/app/web python:2.7 python -m SimpleHTTPServer +``` + +- Retrieve your host address: + +``` + docker-machine ip default +``` + +Then, with your containers running and your host address, you can access the web view in your browser: + +`http://:8000 (eg http://192.168.99.100:8000)` + + +####Errors + +- An error occurred trying to connect: + +Make sure your virtual machine is started, and your environment variables are set in your shell: + +``` +docker-machine start default +docker-machine env default +``` + +- Unable to find image 'pokemongo-bot:latest' locally: + +Make sure that the name of the image is correct. + + ###Using Docker compose + if docker-compose [installed](https://docs.docker.com/compose/install/) you can alternatively run the PokemonGo-Bot ecosystem with one simple command: (by using the docker-compose.yml configuration in this repo) @@ -97,13 +133,21 @@ if docker-compose [installed](https://docs.docker.com/compose/install/) you can docker-compose up ``` +An example of routing the bot's traffic through a tor proxy can be found within the docker-compose_tor.yml file. To use a different file, supply the file name to docker-compose. The d flag is used to run this in detached mode as the tor logs overwhelm any bot logs you might wish to view. The bot logs can still be seen through `docker logs` command. + +``` +docker-compose -f docker-compose_tor.yml up -d +``` + Also run one single service from the compose configuration is possible: ``` docker-compose run --rm bot1-pokego ``` -command for remove all stopped containers: `docker-compose rm` + + +command to stop and remove all stopped containers: `docker-compose down` TODO: Add infos / configuration for running multiple bot instances. diff --git a/docs/manual_installation.md b/docs/manual_installation.md index ce15240e25..0fc3254c6b 100644 --- a/docs/manual_installation.md +++ b/docs/manual_installation.md @@ -7,7 +7,7 @@ ### Linux and Mac Ubuntu will be used for the Linux Example -####First install requierd packages +####First install required packages #####Linux ```bash @@ -91,49 +91,43 @@ source bin/activate ### Windows -#### Windows vista, 7, 8: -Go to : http://pyyaml.org/wiki/PyYAML , download the right version for your pc and install it -##### Windows 10: -Go to [this](http://www.lfd.uci.edu/~gohlke/pythonlibs/#pyyaml) page and download: PyYAML-3.11-cp27-cp27m-win32.whl -(If running 64-bit python or if you get a 'not a supported wheel on this platform' error, -download the 64 bit version instead: PyYAML-3.11-cp27-cp27m-win_amd64.whl ) +##### Requirements -*(Run the following commands from Git Bash.)* +- [Python 2.7.x](http://docs.python-guide.org/en/latest/starting/installation/) +- [git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) +- [Microsoft Visual C++ Compiler for Python 2.7](http://www.microsoft.com/en-us/download/details.aspx?id=44266) -``` -// switch to the directory where you downloaded PyYAML -$ cd download-directory -// install 32-bit version -$ pip2 install PyYAML-3.11-cp27-cp27m-win32.whl -// if you need to install the 64-bit version, do this instead: -// pip2 install PyYAML-3.11-cp27-cp27m-win_amd64.whl -``` -After this, just do: +*Run the following commands in the Command Prompt with Administrator Privileges* ``` -$ git clone -b master https://github.com/PokemonGoF/PokemonGo-Bot -$ cd PokemonGo-Bot -$ virtualenv . -$ script\activate -$ pip2 install -r requirements.txt -$ git submodule init -$ git submodule update +cd C:\Python27\ +pip2 install --upgrade pip +pip2 install --upgrade virtualenv +pip2 install --upgrade protobuf==3.0.0b4 +git clone --recursive -b dev https://github.com/PokemonGoF/PokemonGo-Bot +pip2 install --upgrade "C:\Python27\PokemonGo-Bot\windows_bat\PyYAML-3.11-cp27-cp27m-win32.whl" +pip2 install --upgrade "C:\Python27\PokemonGo-Bot\windows_bat\PyYAML-3.11-cp27-cp27m-win_amd64.whl" +pip2 install --upgrade -r C:/Python27/PokemonGo-Bot/requirements.txt +cd C:/Python27/PokemonGo-Bot/ +virtualenv . +call C:\Python27\PokemonGo-Bot\Scripts\activate.bat +pip2 install --upgrade -r C:/Python27/PokemonGo-Bot/requirements.txt ``` -##### Get encrypt.so (Windows part writing need fine tune) -Due to copywrite on the encrypt.so we are not directly hosting it. Please find a copy elsewhere on the internet and compile it yourself. We accept no responsibility should you encounter any problems with files you download elsewhere. +##### Get encrypt.so and encrypt.dll or encrypt_64.dll +Due to copywrite on the encrypt.so, encrypt.dll and encrypt_64.dll we are not directly hosting it. Please find a copy elsewhere on the internet and compile it yourself. We accept no responsibility should you encounter any problems with files you download elsewhere. +Try asking around our Slack chat! -Ensure you are in the PokemonGo-Bot main folder and run: - -`wget http://pgoapi.com/pgoencrypt.tar.gz && tar -xf pgoencrypt.tar.gz && cd pgoencrypt/src/ && make && mv libencrypt.so ../../encrypt.so && cd ../..` +Download it to the `C:/Python27/PokemonGo-Bot/` folder ##### Update -To update your project do (in the project folder): `git pull` - -To update python requirement packages do (in the project folder): `pip install --upgrade -r requirements.txt` - - +*Run the following commands in the Command Prompt with Administrator Privileges* +``` +cd C:/Python27/PokemonGo-Bot/ +git pull +git submodule update --init --recursive +``` diff --git a/json-validate.py b/json-validate.py new file mode 100644 index 0000000000..00061ba256 --- /dev/null +++ b/json-validate.py @@ -0,0 +1,44 @@ +#! /usr/bin/env python +''' +Check whether a json file is loadable +''' + +import json +import sys + +passed = 0 +failed = 0 +errors = list() + +def check(filename): + global passed, failed + + print "CHECKING ", filename + + f = open(filename).read() + try: + _ = json.loads(f) + print "PASSED: ", filename + passed += 1 + return True + except ValueError as e: + failed += 1 + print "FAILED: ", filename + errors.append("FILE: " + filename) + errors.append(e) + return False + + return False + +if __name__ == "__main__": + for filename in sys.argv[1:]: + check(filename) + + print "Passed: " + str(passed) + " Failed: " + str(failed) + print "\n" + print "Showing errors:" + if failed > 0: + for err in errors: + print err + + sys.exit("JSON check Failed with errors") diff --git a/pokecli.py b/pokecli.py index 2f99d17cbe..55348cae4c 100644 --- a/pokecli.py +++ b/pokecli.py @@ -447,51 +447,6 @@ def _json_loader(filename): type=bool, default=True ) - add_config( - parser, - load, - short_flag="-cte", - long_flag="--catch_throw_parameters.excellent_rate", - help="Define the odd of performing an excellent throw", - type=float, - default=1 - ) - add_config( - parser, - load, - short_flag="-ctg", - long_flag="--catch_throw_parameters.great_rate", - help="Define the odd of performing a great throw", - type=float, - default=0 - ) - add_config( - parser, - load, - short_flag="-ctn", - long_flag="--catch_throw_parameters.nice_rate", - help="Define the odd of performing a nice throw", - type=float, - default=0 - ) - add_config( - parser, - load, - short_flag="-ctm", - long_flag="--catch_throw_parameters.normal_rate", - help="Define the odd of performing a normal throw", - type=float, - default=0 - ) - add_config( - parser, - load, - short_flag="-cts", - long_flag="--catch_throw_parameters.spin_success_rate", - help="Define the odds of performing a spin throw (Value between 0 (never) and 1 (always))", - type=float, - default=1 - ) add_config( parser, load, @@ -507,14 +462,11 @@ def _json_loader(filename): if not config.password and 'password' not in load: config.password = getpass("Password: ") - config.encrypt_location = load.get('encrypt_location','') + config.encrypt_location = load.get('encrypt_location', '') config.catch = load.get('catch', {}) config.release = load.get('release', {}) - config.action_wait_max = load.get('action_wait_max', 4) - config.action_wait_min = load.get('action_wait_min', 1) config.plugins = load.get('plugins', []) config.raw_tasks = load.get('tasks', []) - config.min_ultraball_to_keep = load.get('min_ultraball_to_keep', None) config.vips = load.get('vips', {}) @@ -538,8 +490,10 @@ def task_configuration_error(flag_name): """.format(flag_name)) old_flags = ['mode', 'catch_pokemon', 'spin_forts', 'forts_spin', 'hatch_eggs', 'release_pokemon', 'softban_fix', - 'longer_eggs_first', 'evolve_speed', 'use_lucky_egg', 'item_filter', 'evolve_all', 'evolve_cp_min', - 'max_steps'] + 'longer_eggs_first', 'evolve_speed', 'use_lucky_egg', 'item_filter', 'evolve_all', 'evolve_cp_min', + 'max_steps', 'catch_throw_parameters.excellent_rate', 'catch_throw_parameters.great_rate', + 'catch_throw_parameters.nice_rate', 'catch_throw_parameters.normal_rate', + 'catch_throw_parameters.spin_success_rate'] for flag in old_flags: if flag in load: task_configuration_error(flag) diff --git a/pokemongo_bot/__init__.py b/pokemongo_bot/__init__.py index 9efea4071d..eaa6572056 100644 --- a/pokemongo_bot/__init__.py +++ b/pokemongo_bot/__init__.py @@ -29,6 +29,7 @@ from pokemongo_bot.socketio_server.runner import SocketIoRunner from pokemongo_bot.websocket_remote_control import WebsocketRemoteControl from pokemongo_bot.base_dir import _base_dir +from pokemongo_bot.datastore import DatabaseManager, Datastore from worker_result import WorkerResult from tree_config_builder import ConfigException, MismatchTaskApiVersion, TreeConfigBuilder from inventory import init_inventory @@ -36,7 +37,7 @@ import struct -class PokemonGoBot(object): +class PokemonGoBot(Datastore): @property def position(self): return self.api._position_lat, self.api._position_lng, 0 @@ -56,6 +57,9 @@ def player_data(self): def __init__(self, config): self.config = config + self.database = DatabaseManager(self) + super(PokemonGoBot, self).__init__() + self.fort_timeouts = dict() self.pokemon_list = json.load( open(os.path.join(_base_dir, 'data', 'pokemon.json')) @@ -79,8 +83,13 @@ def __init__(self, config): self.web_update_queue = Queue.Queue(maxsize=1) self.web_update_thread = threading.Thread(target=self.update_web_location_worker) self.web_update_thread.start() + + # Heartbeat limiting self.heartbeat_threshold = self.config.heartbeat_threshold self.heartbeat_counter = 0 + self.last_heartbeat = time.time() + + def start(self): self._setup_event_system() self._setup_logging() @@ -160,7 +169,10 @@ def _register_events(self): ) self.event_manager.register_event( 'bot_sleep', - parameters=('time_in_seconds',) + parameters=( + 'time_hms', + 'wake' + ) ) # fort stuff @@ -413,7 +425,7 @@ def _register_events(self): self.event_manager.register_event( 'arrived_at_cluster', parameters=( - 'forts', 'radius' + 'num_points', 'forts', 'radius' ) ) @@ -671,6 +683,9 @@ def login(self): ) time.sleep(10) + with self.database.backend.connection as conn: + conn.execute('''INSERT INTO login (timestamp, message) VALUES (?, ?)''', (time.time(), 'LOGIN_SUCCESS')) + self.event_manager.emit( 'login_successful', sender=self, @@ -679,14 +694,14 @@ def login(self): ) def get_encryption_lib(self): - if _platform == "linux" or _platform == "linux2" or _platform == "darwin" or _platform == "freebsd10": - file_name = 'encrypt.so' - elif _platform == "Windows" or _platform == "win32": + if _platform == "Windows" or _platform == "win32": # Check if we are on 32 or 64 bit if sys.maxsize > 2**32: file_name = 'encrypt_64.dll' else: file_name = 'encrypt.dll' + else: + file_name = 'encrypt.so' if self.config.encrypt_location == '': path = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir)) @@ -1015,12 +1030,13 @@ def get_pos_by_name(self, location_name): def heartbeat(self): # Remove forts that we can now spin again. + now = time.time() self.fort_timeouts = {id: timeout for id, timeout in self.fort_timeouts.iteritems() - if timeout >= time.time() * 1000} - self.heartbeat_counter = self.heartbeat_counter + 1 - if self.heartbeat_counter >= self.heartbeat_threshold: - self.heartbeat_counter = 0 + if timeout >= now * 1000} + + if now - self.last_heartbeat >= self.heartbeat_threshold: + self.last_heartbeat = now request = self.api.create_request() request.get_player() request.check_awarded_badges() diff --git a/pokemongo_bot/cell_workers/__init__.py b/pokemongo_bot/cell_workers/__init__.py index 538673cfe7..217eebbebf 100644 --- a/pokemongo_bot/cell_workers/__init__.py +++ b/pokemongo_bot/cell_workers/__init__.py @@ -19,3 +19,4 @@ from follow_cluster import FollowCluster from sleep_schedule import SleepSchedule from update_live_stats import UpdateLiveStats +from catch_pokemon import CatchPokemon diff --git a/pokemongo_bot/cell_workers/catch_lured_pokemon.py b/pokemongo_bot/cell_workers/catch_lured_pokemon.py index afddeb53d5..5c1456b88f 100644 --- a/pokemongo_bot/cell_workers/catch_lured_pokemon.py +++ b/pokemongo_bot/cell_workers/catch_lured_pokemon.py @@ -60,13 +60,14 @@ def get_lured_pokemon(self): self.emit_event( 'lured_pokemon_found', + level='info', formatted='Lured pokemon at fort {fort_name} ({fort_id})', data=result ) return pokemon_to_catch def catch_pokemon(self, pokemon): - worker = PokemonCatchWorker(pokemon, self.bot) + worker = PokemonCatchWorker(pokemon, self.bot, self.config) return_value = worker.work() return return_value diff --git a/pokemongo_bot/cell_workers/catch_pokemon.py b/pokemongo_bot/cell_workers/catch_pokemon.py new file mode 100644 index 0000000000..b0500f6d1a --- /dev/null +++ b/pokemongo_bot/cell_workers/catch_pokemon.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +from pokemongo_bot.base_task import BaseTask +from pokemongo_bot.worker_result import WorkerResult +from pokemongo_bot.cell_workers import CatchVisiblePokemon, CatchLuredPokemon + + +class CatchPokemon(BaseTask): + SUPPORTED_TASK_API_VERSION = 1 + + def initialize(self): + self.catch_workers = [] + if self.config.get('catch_visible_pokemon', True): + self.catch_workers.append(CatchVisiblePokemon(self.bot, self.config)) + if self.config.get('catch_lured_pokemon', True): + self.catch_workers.append(CatchLuredPokemon(self.bot, self.config)) + + def work(self): + for cw in self.catch_workers: + if cw.work() == WorkerResult.RUNNING: + return WorkerResult.RUNNING + return WorkerResult.SUCCESS diff --git a/pokemongo_bot/cell_workers/catch_visible_pokemon.py b/pokemongo_bot/cell_workers/catch_visible_pokemon.py index 0203459c28..9c9dfb8024 100644 --- a/pokemongo_bot/cell_workers/catch_visible_pokemon.py +++ b/pokemongo_bot/cell_workers/catch_visible_pokemon.py @@ -66,7 +66,7 @@ def work(self): return WorkerResult.SUCCESS def catch_pokemon(self, pokemon): - worker = PokemonCatchWorker(pokemon, self.bot) + worker = PokemonCatchWorker(pokemon, self.bot, self.config) return_value = worker.work() return return_value diff --git a/pokemongo_bot/cell_workers/follow_cluster.py b/pokemongo_bot/cell_workers/follow_cluster.py index 27008dcdf8..1e227c0343 100644 --- a/pokemongo_bot/cell_workers/follow_cluster.py +++ b/pokemongo_bot/cell_workers/follow_cluster.py @@ -21,10 +21,10 @@ def work(self): log_lure_avail_str = '' log_lured_str = '' if self.lured: - log_lured_str = 'lured ' - lured_forts = [x for x in forts if 'lure_info' in x] + lured_forts = [x for x in forts if 'active_fort_modifier' in x] if len(lured_forts) > 0: - self.dest = find_biggest_cluster(self.radius, lured_forts, 'lure_info') + log_lured_str = 'lured ' + self.dest = find_biggest_cluster(self.radius, lured_forts, '9QM=') else: log_lure_avail_str = 'No lured pokestops in vicinity. Search for normal ones instead. ' self.dest = find_biggest_cluster(self.radius, forts) @@ -39,7 +39,7 @@ def work(self): if not self.is_at_destination: msg = log_lure_avail_str + ( - "Move to destiny {num_points}. {forts} " + "Move to cluster: {num_points} {forts} " "pokestops will be in range of {radius}. Walking {distance}m." ) self.emit_event( @@ -49,7 +49,7 @@ def work(self): 'num_points': cnt, 'forts': log_lured_str, 'radius': str(self.radius), - 'distance': str(distance(self.bot.position[0], self.bot.position[1], lat, lng)) + 'distance': str(round(distance(self.bot.position[0], self.bot.position[1], lat, lng), 2)) } ) @@ -71,9 +71,10 @@ def work(self): elif not self.announced: self.emit_event( 'arrived_at_cluster', - formatted="Arrived at cluster. {forts} are in a range of {radius}m radius.", + formatted="Arrived at cluster. {num_points} {forts} pokestops are in a range of {radius}m radius.", data={ - 'forts': str(cnt), + 'num_points': cnt, + 'forts': log_lured_str, 'radius': self.radius } ) diff --git a/pokemongo_bot/cell_workers/follow_path.py b/pokemongo_bot/cell_workers/follow_path.py index 055150070a..4f98cb93fd 100644 --- a/pokemongo_bot/cell_workers/follow_path.py +++ b/pokemongo_bot/cell_workers/follow_path.py @@ -136,10 +136,10 @@ def work(self): self.emit_event( 'position_update', - formatted="Walking from {last_position} to {current_position} ({distance} {distance_unit})", + formatted="Walk to {last_position} now at {current_position}, distance left: ({distance} {distance_unit}) ..", data={ - 'last_position': (last_lat, last_lng, 0), - 'current_position': (lat, lng, 0), + 'last_position': (lat, lng, 0), + 'current_position': (last_lat, last_lng, 0), 'distance': dist, 'distance_unit': 'm' } diff --git a/pokemongo_bot/cell_workers/move_to_map_pokemon.py b/pokemongo_bot/cell_workers/move_to_map_pokemon.py index 8a4ae9ca27..a598a9f69f 100644 --- a/pokemongo_bot/cell_workers/move_to_map_pokemon.py +++ b/pokemongo_bot/cell_workers/move_to_map_pokemon.py @@ -85,6 +85,9 @@ def initialize(self): self.caught = [] self.min_ball = self.config.get('min_ball', 1) self.map_path = self.config.get('map_path', 'raw_data') + self.snipe_high_prio_only = self.config.get('snipe_high_prio_only', False) + self.snipe_high_prio_threshold = self.config.get('snipe_high_prio_threshold', 400) + data_file = os.path.join(_base_dir, 'map-caught-{}.json'.format(self.bot.config.username)) if os.path.isfile(data_file): @@ -124,9 +127,6 @@ def get_pokemon_from_map(self): if pokemon['name'] not in self.config['catch'] and not pokemon['is_vip']: continue - if pokemon['disappear_time'] < (now + self.config['min_time']): - continue - if self.was_caught(pokemon): continue @@ -142,6 +142,11 @@ def get_pokemon_from_map(self): if pokemon['dist'] > self.config['max_distance'] and not self.config['snipe']: continue + # pokemon not reachable with mean walking speed (by config) + mean_walk_speed = (self.bot.config.walk_max + self.bot.config.walk_min) / 2 + if pokemon['dist'] > ((pokemon['disappear_time'] - now) * mean_walk_speed) and not self.config['snipe']: + continue + pokemon_list.append(pokemon) return pokemon_list @@ -212,7 +217,7 @@ def snipe(self, pokemon): last_position = self.bot.position[0:2] self.bot.heartbeat() self._teleport_to(pokemon) - catch_worker = PokemonCatchWorker(pokemon, self.bot) + catch_worker = PokemonCatchWorker(pokemon, self.bot, self.config) api_encounter_response = catch_worker.create_encounter_api_call() time.sleep(SNIPE_SLEEP_SEC) self._teleport_back(last_position) @@ -252,13 +257,16 @@ def work(self): pokemon = pokemon_list[0] - # if we only have ultraballs and the target is not a vip don't snipe/walk - if (pokeballs + superballs) < self.min_ball and not pokemon['is_vip']: - return WorkerResult.SUCCESS + if pokeballs < 1: + if superballs < 1: + if ultraballs < 1: + return WorkerResult.SUCCESS + if not pokemon['is_vip']: + return WorkerResult.SUCCESS if self.config['snipe']: - if self.config['snipe_high_prio_only']: - if self.config['snipe_high_prio_threshold'] < pokemon['priority'] or pokemon['is_vip']: + if self.snipe_high_prio_only: + if self.snipe_high_prio_threshold < pokemon['priority'] or pokemon['is_vip']: self.snipe(pokemon) else: return self.snipe(pokemon) diff --git a/pokemongo_bot/cell_workers/pokemon_catch_worker.py b/pokemongo_bot/cell_workers/pokemon_catch_worker.py index 49a96690fe..ff524dc740 100644 --- a/pokemongo_bot/cell_workers/pokemon_catch_worker.py +++ b/pokemongo_bot/cell_workers/pokemon_catch_worker.py @@ -1,10 +1,10 @@ # -*- coding: utf-8 -*- import time -from random import random +from random import random, randrange from pokemongo_bot import inventory from pokemongo_bot.base_task import BaseTask -from pokemongo_bot.human_behaviour import sleep +from pokemongo_bot.human_behaviour import sleep, action_delay from pokemongo_bot.inventory import Pokemon from pokemongo_bot.worker_result import WorkerResult @@ -29,23 +29,48 @@ class PokemonCatchWorker(BaseTask): - def __init__(self, pokemon, bot): + def __init__(self, pokemon, bot, config): self.pokemon = pokemon self.api = bot.api self.bot = bot self.position = bot.position - self.config = bot.config self.pokemon_list = bot.pokemon_list self.inventory = inventory.items() self.spawn_point_guid = '' self.response_key = '' self.response_status_key = '' + + #Config + self.config = config + self.min_ultraball_to_keep = config.get('min_ultraball_to_keep', 10) + + self.catch_throw_parameters = config.get('catch_throw_parameters', {}) + self.catch_throw_parameters_spin_success_rate = self.catch_throw_parameters.get('spin_success_rate', 0.6) + self.catch_throw_parameters_excellent_rate = self.catch_throw_parameters.get('excellent_rate', 0.1) + self.catch_throw_parameters_great_rate = self.catch_throw_parameters.get('great_rate', 0.5) + self.catch_throw_parameters_nice_rate = self.catch_throw_parameters.get('nice_rate', 0.3) + self.catch_throw_parameters_normal_rate = self.catch_throw_parameters.get('normal_rate', 0.1) + + self.catchsim_config = config.get('catch_simulation', {}) + self.catchsim_catch_wait_min = self.catchsim_config.get('catch_wait_min', 2) + self.catchsim_catch_wait_max = self.catchsim_config.get('catch_wait_max', 6) + self.catchsim_flee_count = int(self.catchsim_config.get('flee_count', 3)) + self.catchsim_flee_duration = self.catchsim_config.get('flee_duration', 2) + self.catchsim_berry_wait_min = self.catchsim_config.get('berry_wait_min', 2) + self.catchsim_berry_wait_max = self.catchsim_config.get('berry_wait_max', 3) + self.catchsim_changeball_wait_min = self.catchsim_config.get('changeball_wait_min', 2) + self.catchsim_changeball_wait_max = self.catchsim_config.get('changeball_wait_max', 3) + ############################################################################ # public methods ############################################################################ def work(self, response_dict=None): + pokeballs = self.bot.item_inventory_count(1) + superballs = self.bot.item_inventory_count(2) + ultraballs = self.bot.item_inventory_count(3) + response_dict = response_dict or self.create_encounter_api_call() # validate response @@ -72,6 +97,14 @@ def work(self, response_dict=None): if not self._should_catch_pokemon(pokemon): return WorkerResult.SUCCESS + is_vip = self._is_vip_pokemon(pokemon) + if pokeballs < 1: + if superballs < 1: + if ultraballs < 1: + return WorkerResult.SUCCESS + if not is_vip: + return WorkerResult.SUCCESS + # log encounter self.emit_event( 'pokemon_appeared', @@ -92,7 +125,6 @@ def work(self, response_dict=None): sleep(3) # check for VIP pokemon - is_vip = self._is_vip_pokemon(pokemon) if is_vip: self.emit_event('vip_pokemon', formatted='This is a VIP pokemon. Catch!!!') @@ -166,18 +198,20 @@ def _pokemon_matches_config(self, config, pokemon, default_logic='and'): return LOGIC_TO_FUNCTION[pokemon_config.get('logic', default_logic)](*catch_results.values()) def _should_catch_pokemon(self, pokemon): - return self._pokemon_matches_config(self.config.catch, pokemon) + return self._pokemon_matches_config(self.bot.config.catch, pokemon) def _is_vip_pokemon(self, pokemon): # having just a name present in the list makes them vip - if self.config.vips.get(pokemon.name) == {}: + if self.bot.config.vips.get(pokemon.name) == {}: return True - return self._pokemon_matches_config(self.config.vips, pokemon, default_logic='or') + return self._pokemon_matches_config(self.bot.config.vips, pokemon, default_logic='or') def _pct(self, rate_by_ball): return '{0:.2f}'.format(rate_by_ball * 100) def _use_berry(self, berry_id, berry_count, encounter_id, catch_rate_by_ball, current_ball): + # Delay to simulate selecting berry + action_delay(self.catchsim_berry_wait_min, self.catchsim_berry_wait_max) new_catch_rate_by_ball = [] self.emit_event( 'pokemon_catch_rate', @@ -254,9 +288,9 @@ def _do_catch(self, pokemon, encounter_id, catch_rate_by_ball, is_vip=False): # use `min_ultraball_to_keep` from config if is not None min_ultraball_to_keep = ball_count[ITEM_ULTRABALL] - if self.config.min_ultraball_to_keep is not None: - if self.config.min_ultraball_to_keep >= 0 and self.config.min_ultraball_to_keep < min_ultraball_to_keep: - min_ultraball_to_keep = self.config.min_ultraball_to_keep + if self.min_ultraball_to_keep is not None: + if self.min_ultraball_to_keep >= 0 and self.min_ultraball_to_keep < min_ultraball_to_keep: + min_ultraball_to_keep = self.min_ultraball_to_keep while True: @@ -286,6 +320,7 @@ def _do_catch(self, pokemon, encounter_id, catch_rate_by_ball, is_vip=False): # use a berry if we are under our ideal rate and have berries to spare used_berry = False + changed_ball = False if catch_rate_by_ball[current_ball] < ideal_catch_rate_before_throw and berries_to_spare: new_catch_rate_by_ball = self._use_berry(berry_id, berry_count, encounter_id, catch_rate_by_ball, current_ball) if new_catch_rate_by_ball != catch_rate_by_ball: @@ -301,6 +336,7 @@ def _do_catch(self, pokemon, encounter_id, catch_rate_by_ball, is_vip=False): if catch_rate_by_ball[current_ball] < ideal_catch_rate_before_throw and ball_count[best_ball] > 0: # if current ball chance to catch is under our ideal rate, and player has better ball - then use it current_ball = best_ball + changed_ball = True # if the rate is still low and we didn't throw a berry before, throw one if catch_rate_by_ball[current_ball] < ideal_catch_rate_before_throw and berry_count > 0 and not used_berry: @@ -309,6 +345,11 @@ def _do_catch(self, pokemon, encounter_id, catch_rate_by_ball, is_vip=False): catch_rate_by_ball = new_catch_rate_by_ball self.inventory.get(ITEM_RAZZBERRY).remove(1) berry_count -= 1 + used_berry = True + + # If we change ball then wait to simulate user selecting it + if changed_ball: + action_delay(self.catchsim_changeball_wait_min, self.catchsim_changeball_wait_max) # Randomize the quality of the throw # Default structure @@ -323,6 +364,8 @@ def _do_catch(self, pokemon, encounter_id, catch_rate_by_ball, is_vip=False): # TODO : Log which type of throw we selected ball_count[current_ball] -= 1 self.inventory.get(current_ball).remove(1) + # Take some time to throw the ball from config options + action_delay(self.catchsim_catch_wait_min, self.catchsim_catch_wait_max) self.emit_event( 'threw_pokeball', formatted='Used {ball_name}, with chance {success_percentage} ({count_left} left)', @@ -355,7 +398,13 @@ def _do_catch(self, pokemon, encounter_id, catch_rate_by_ball, is_vip=False): formatted='{pokemon} capture failed.. trying again!', data={'pokemon': pokemon.name} ) - sleep(2) + + # 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 + # multiplies this by flee_duration to get total sleep + if self.catchsim_flee_count: + sleep((randrange(self.catchsim_flee_count)+1) * self.catchsim_flee_duration) + continue # abandon if pokemon vanished @@ -411,17 +460,17 @@ def _do_catch(self, pokemon, encounter_id, catch_rate_by_ball, is_vip=False): break def generate_spin_parameter(self, throw_parameters): - spin_success_rate = self.config.catch_throw_parameters_spin_success_rate + spin_success_rate = self.catch_throw_parameters_spin_success_rate if random() <= spin_success_rate: throw_parameters['spin_modifier'] = 0.5 + 0.5 * random() else: throw_parameters['spin_modifier'] = 0.499 * random() def generate_throw_quality_parameters(self, throw_parameters): - throw_excellent_chance = self.config.catch_throw_parameters_excellent_rate - throw_great_chance = self.config.catch_throw_parameters_great_rate - throw_nice_chance = self.config.catch_throw_parameters_nice_rate - throw_normal_throw_chance = self.config.catch_throw_parameters_normal_rate + throw_excellent_chance = self.catch_throw_parameters_excellent_rate + throw_great_chance = self.catch_throw_parameters_great_rate + throw_nice_chance = self.catch_throw_parameters_nice_rate + throw_normal_throw_chance = self.catch_throw_parameters_normal_rate # Total every chance types, pick a random number in the range and check what type of throw we got total_chances = throw_excellent_chance + throw_great_chance \ diff --git a/pokemongo_bot/cell_workers/pokemon_optimizer.py b/pokemongo_bot/cell_workers/pokemon_optimizer.py index 81f78ddf33..682c5ad007 100644 --- a/pokemongo_bot/cell_workers/pokemon_optimizer.py +++ b/pokemongo_bot/cell_workers/pokemon_optimizer.py @@ -1,52 +1,71 @@ import copy -import logging +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.human_behaviour import sleep, action_delay from pokemongo_bot.item_list import Item from pokemongo_bot.worker_result import WorkerResult +SUCCESS = 1 +ERROR_INVALID_ITEM_TYPE = 2 +ERROR_XP_BOOST_ALREADY_ACTIVE = 3 +ERROR_NO_ITEMS_REMAINING = 4 +ERROR_LOCATION_UNSET = 5 + class PokemonOptimizer(BaseTask): SUPPORTED_TASK_API_VERSION = 1 def initialize(self): self.family_by_family_id = {} + self.max_pokemon_storage = 0 self.last_pokemon_count = 0 - self.logger = logging.getLogger(self.__class__.__name__) self.config_transfer = self.config.get("transfer", False) self.config_evolve = self.config.get("evolve", False) - self.config_use_candies_for_xp = self.config.get("use_candies_for_xp", True) - self.config_use_lucky_egg = self.config.get("use_lucky_egg", False) - self.config_evolve_only_with_lucky_egg = self.config.get("evolve_only_with_lucky_egg", True) - self.config_minimum_evolve_for_lucky_egg = self.config.get("minimum_evolve_for_lucky_egg", 90) + self.config_evolve_time = self.config.get("evolve_time", 20) + 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", 90) + 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.transfer_wait_min = self.config.get('transfer_wait_min', 1) + self.transfer_wait_max = self.config.get('transfer_wait_max', 4) + def get_pokemon_slot_left(self): pokemon_count = len(inventory.pokemons()._data) - + self.max_pokemon_storage = self.bot.player_data["max_pokemon_storage"] + if pokemon_count != self.last_pokemon_count: self.last_pokemon_count = pokemon_count - self.logger.info("Pokemon Bag: %s/%s", pokemon_count, self.bot._player["max_pokemon_storage"]) - - return self.bot._player["max_pokemon_storage"] - pokemon_count + self.logger.info("Pokemon Bag: %s/%s", pokemon_count, self.max_pokemon_storage) + + return self.max_pokemon_storage - pokemon_count def work(self): - if self.get_pokemon_slot_left() > 5: + if (not self.enabled) or (self.get_pokemon_slot_left() > 5): return WorkerResult.SUCCESS - self.parse_inventory() + self.open_inventory() + self.save_web_inventory() transfer_all = [] evo_all_best = [] evo_all_crap = [] for family_id, family in self.family_by_family_id.items(): - transfer, evo_best, evo_crap = self.get_family_optimized(family_id, family) + 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) + transfer_all += transfer evo_all_best += evo_best evo_all_crap += evo_crap @@ -54,14 +73,13 @@ def work(self): evo_all = evo_all_best + evo_all_crap self.apply_optimization(transfer_all, evo_all) - inventory.refresh_inventory() return WorkerResult.SUCCESS - def parse_inventory(self): + def open_inventory(self): self.family_by_family_id.clear() - for pokemon in inventory.pokemons().all(): + for pokemon in inventory.pokemons(True).all(): family_id = pokemon.first_evolution_id setattr(pokemon, "ncp", pokemon.cp_percent) setattr(pokemon, "dps", pokemon.moveset.dps) @@ -70,10 +88,14 @@ def parse_inventory(self): self.family_by_family_id.setdefault(family_id, []).append(pokemon) - def get_family_optimized(self, family_id, family): - if family_id == 133: # "Eevee" - return self.get_multi_family_optimized(family_id, family, 3) + def save_web_inventory(self): + inventory_items = self.bot.get_inventory()["responses"]["GET_INVENTORY"]["inventory_delta"]["inventory_items"] + web_inventory = os.path.join(_base_dir, "web", "inventory-%s.json" % self.bot.config.username) + with open(web_inventory, "w") as outfile: + json.dump(inventory_items, outfile) + + def get_family_optimized(self, family_id, family): evolve_best = [] keep_best = [] @@ -95,16 +117,12 @@ def get_multi_family_optimized(self, family_id, family, nb_branch): 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} - transfer_senior = [] - - for senior_pid, senior_family in senior_grouped_family.items(): - transfer_senior += self.get_family_optimized(senior_pid, senior_family)[0] - - if len(senior_pids) < nb_branch: + 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 Pokemons are potentially good to keep - evolve_best = other_family - evolve_best.sort(key=lambda p: p.iv * p.ncp, reverse=True) - keep_best = [] + 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) else: evolve_best = [] keep_best = [] @@ -124,16 +142,22 @@ def get_multi_family_optimized(self, family_id, family, nb_branch): evolve_best = self.unique_pokemons(evolve_best) keep_best = self.unique_pokemons(keep_best) + transfer, evo_best, evo_crap = self.get_evolution_plan(family_id, other_family, evolve_best, keep_best) - transfer, evo_best, evo_crap = self.get_evolution_plan(family_id, other_family, evolve_best, keep_best) - transfer += transfer_senior + for senior_pid, senior_family in senior_grouped_family.items(): + transfer += self.get_family_optimized(senior_pid, senior_family)[0] return (transfer, evo_best, evo_crap) def get_top_rank(self, family, criteria): sorted_family = self.get_sorted_family(family, criteria) - worst = sorted_family[criteria.get("top", 1) - 1] - return [p for p in sorted_family if self.get_rank(p, criteria) >= self.get_rank(worst, criteria)] + index = criteria.get("top", 1) - 1 + + 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)] + else: + return sorted_family 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)] @@ -158,7 +182,7 @@ def get_evolution_plan(self, family_id, family, evolve_best, keep_best): 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.sort(key=lambda p: p.iv, reverse=True) + crap.sort(key=lambda p: p.iv * p.ncp, reverse=True) candies += len(crap) @@ -180,11 +204,11 @@ def get_evolution_plan(self, family_id, family, evolve_best, keep_best): next_pid = pokemon.next_evolution_ids[0] next_evo = copy.copy(pokemon) next_evo.pokemon_id = next_pid - next_evo._static_data = inventory.pokemons().data_for(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) - if self.config_use_candies_for_xp: + 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) @@ -201,6 +225,11 @@ def get_evolution_plan(self, family_id, family, evolve_best, keep_best): keep_for_evo = 0 evo_crap = [p for p in crap if p.has_next_evolution() and p.evolution_cost == junior_evolution_cost][:keep_for_evo] + + # If not much to evolve, better keep the candies + if len(evo_crap) < math.ceil(self.max_pokemon_storage * 0.01): + evo_crap = [] + transfer = [p for p in crap if p not in evo_crap] else: evo_crap = [] @@ -209,25 +238,27 @@ def get_evolution_plan(self, family_id, family, evolve_best, keep_best): return (transfer, can_evolve_best, evo_crap) def apply_optimization(self, transfer, evo): + self.logger.info("Transferring %s Pokemons", len(transfer)) + for pokemon in transfer: self.transfer_pokemon(pokemon) if len(evo) == 0: return - if self.config_evolve and self.config_use_lucky_egg and (not self.bot.config.test): - lucky_egg = inventory.items().get(Item.ITEM_LUCKY_EGG.value) # @UndefinedVariable - - if self.config_evolve_only_with_lucky_egg and (lucky_egg.count == 0): - self.logger.info("Skipping evolution step. No lucky egg available") + if self.config_evolve and self.config_may_use_lucky_egg and (not self.bot.config.test): + if len(evo) >= self.config_evolve_count_for_lucky_egg: + lucky_egg = inventory.items().get(Item.ITEM_LUCKY_EGG.value) # @UndefinedVariable + + if lucky_egg.count > 0: + self.use_lucky_egg() + elif self.config_evolve_only_with_lucky_egg: + self.logger.info("Skipping evolution step. No lucky egg available") + return + elif self.config_evolve_only_with_lucky_egg: + self.logger.info("Skipping evolution step. Not enough Pokemons (%s) to evolve with lucky egg", len(evo)) return - if len(evo) < self.config_minimum_evolve_for_lucky_egg: - self.logger.info("Skipping evolution step. Not enough Pokemons (%s) to evolve", len(evo)) - return - - self.use_lucky_egg() - self.logger.info("Evolving %s Pokemons", len(evo)) for pokemon in evo: @@ -256,7 +287,7 @@ def transfer_pokemon(self, pokemon): inventory.candies().get(pokemon.pokemon_id).add(candy) inventory.pokemons().remove(pokemon.id) - action_delay(self.bot.config.action_wait_min, self.bot.config.action_wait_max) + action_delay(self.transfer_wait_min, self.transfer_wait_max) return True @@ -276,13 +307,18 @@ def use_lucky_egg(self): result = response_dict.get("responses", {}).get("USE_ITEM_XP_BOOST", {}).get("result", 0) - if result == 1: + if result == SUCCESS: lucky_egg.remove(1) self.emit_event("used_lucky_egg", formatted="Used lucky egg ({amount_left} left).", data={"amount_left": lucky_egg.count}) return True + elif result == ERROR_XP_BOOST_ALREADY_ACTIVE: + self.emit_event("used_lucky_egg", + formatted="Lucky egg already active ({amount_left} left).", + data={"amount_left": lucky_egg.count}) + return True else: self.emit_event("lucky_egg_error", level='error', @@ -300,7 +336,7 @@ def evolve_pokemon(self, pokemon): result = response_dict.get("responses", {}).get("EVOLVE_POKEMON", {}).get("result", 0) - if result != 1: + if result != SUCCESS: return False xp = response_dict.get("responses", {}).get("EVOLVE_POKEMON", {}).get("experience_awarded", 0) @@ -323,6 +359,6 @@ def evolve_pokemon(self, pokemon): new_pokemon = inventory.Pokemon(evolution) inventory.pokemons().add(new_pokemon) - sleep(20) + sleep(self.config_evolve_time) return True diff --git a/pokemongo_bot/cell_workers/recycle_items.py b/pokemongo_bot/cell_workers/recycle_items.py index 630072e59e..0d9ca33fd5 100644 --- a/pokemongo_bot/cell_workers/recycle_items.py +++ b/pokemongo_bot/cell_workers/recycle_items.py @@ -52,6 +52,8 @@ def initialize(self): self.max_potions_keep = self.config.get('max_potions_keep', None) self.max_berries_keep = self.config.get('max_berries_keep', None) self.max_revives_keep = self.config.get('max_revives_keep', None) + self.recycle_wait_min = self.config.get('recycle_wait_min', 1) + self.recycle_wait_max = self.config.get('recycle_wait_max', 4) self._validate_item_filter() def _validate_item_filter(self): @@ -75,7 +77,7 @@ def should_run(self): :return: True if the recycling process should be run; otherwise, False. :rtype: bool """ - if inventory.Items.get_space_left() < (DEFAULT_MIN_EMPTY_SPACE if self.min_empty_space is None else self.min_empty_space): + if inventory.Items.get_space_left() <= (DEFAULT_MIN_EMPTY_SPACE if self.min_empty_space is None else self.min_empty_space): return True return False @@ -107,7 +109,7 @@ def work(self): if self.item_should_be_recycled(item_in_inventory): # Make the bot appears more human - action_delay(self.bot.config.action_wait_min, self.bot.config.action_wait_max) + action_delay(self.recycle_wait_min, self.recycle_wait_max) # If at any recycling process call we got an error, we consider that the result of this task is error too. if ItemRecycler(self.bot, item_in_inventory, self.get_amount_to_recycle(item_in_inventory)).work() == WorkerResult.ERROR: worker_result = WorkerResult.ERROR @@ -129,7 +131,7 @@ def recycle_excess_category_max(self, category_max, category_items_list): category_count = category_count + i[1] items_to_recycle = self.get_category_items_to_recycle(category_inventory, category_count, category_max) for item in items_to_recycle: - action_delay(self.bot.config.action_wait_min, self.bot.config.action_wait_max) + action_delay(self.recycle_wait_min, self.recycle_wait_max) if ItemRecycler(self.bot, inventory.items().get(item[0]), item[1]).work() == WorkerResult.ERROR: worker_result = WorkerResult.ERROR return worker_result @@ -165,7 +167,7 @@ def get_category_items_to_recycle(self, category_inventory, category_count, cate items_to_be_recycled = category_count - category_max for item in category_inventory: - if items_to_be_recycled == 0: + if items_to_be_recycled == 0: break if items_to_be_recycled >= item[1]: items_to_recycle.append([]) @@ -175,7 +177,7 @@ def get_category_items_to_recycle(self, category_inventory, category_count, cate items_to_recycle.append([]) items_to_recycle[x].append(item[0]) items_to_recycle[x].append(items_to_be_recycled) - items_to_be_recycled = items_to_be_recycled - items_to_recycle[x][1] + items_to_be_recycled = items_to_be_recycled - items_to_recycle[x][1] x = x + 1 return items_to_recycle diff --git a/pokemongo_bot/cell_workers/sleep_schedule.py b/pokemongo_bot/cell_workers/sleep_schedule.py index 5a7d617e23..e35d469588 100644 --- a/pokemongo_bot/cell_workers/sleep_schedule.py +++ b/pokemongo_bot/cell_workers/sleep_schedule.py @@ -36,9 +36,10 @@ def initialize(self): # self.bot.event_manager.register_event('sleeper_scheduled', parameters=('datetime',)) self._process_config() self._schedule_next_sleep() + self._calculate_current_sleep() def work(self): - if datetime.now() >= self._next_sleep: + if self._should_sleep_now(): self._sleep() self._schedule_next_sleep() self.bot.login() @@ -71,6 +72,20 @@ def _schedule_next_sleep(self): } ) + def _calculate_current_sleep(self): + self._current_sleep = self._next_sleep - timedelta(days=1) + current_duration = self._get_next_duration() + self._current_end = self._current_sleep + timedelta(seconds = current_duration) + + def _should_sleep_now(self): + if datetime.now() >= self._next_sleep: + return True + if datetime.now() >= self._current_sleep and datetime.now() < self._current_end: + self._next_duration = (self._current_end - datetime.now()).total_seconds() + return True + + return False + def _get_next_sleep_schedule(self): now = datetime.now() + self.SCHEDULING_MARGIN next_time = now.replace(hour=self.time.hour, minute=self.time.minute) @@ -93,11 +108,20 @@ def _get_random_offset(self, max_offset): def _sleep(self): sleep_to_go = self._next_duration + + sleep_m, sleep_s = divmod(sleep_to_go, 60) + sleep_h, sleep_m = divmod(sleep_m, 60) + sleep_hms = '%02d:%02d:%02d' % (sleep_h, sleep_m, sleep_s) + + now = datetime.now() + wake = str(now + timedelta(seconds=sleep_to_go)) + self.emit_event( 'bot_sleep', - formatted="Sleeping for {time_in_seconds}", + formatted="Sleeping for {time_hms}, wake at {wake}", data={ - 'time_in_seconds': sleep_to_go + 'time_hms': sleep_hms, + 'wake': wake } ) while sleep_to_go > 0: diff --git a/pokemongo_bot/cell_workers/spin_fort.py b/pokemongo_bot/cell_workers/spin_fort.py index 4d4b8b001f..f49d2c1408 100644 --- a/pokemongo_bot/cell_workers/spin_fort.py +++ b/pokemongo_bot/cell_workers/spin_fort.py @@ -9,7 +9,7 @@ from pokemongo_bot import inventory from pokemongo_bot.constants import Constants -from pokemongo_bot.human_behaviour import sleep +from pokemongo_bot.human_behaviour import action_delay from pokemongo_bot.worker_result import WorkerResult from pokemongo_bot.base_task import BaseTask from pokemongo_bot.base_dir import _base_dir @@ -26,6 +26,8 @@ class SpinFort(BaseTask): def initialize(self): self.ignore_item_count = self.config.get("ignore_item_count", False) + self.spin_wait_min = self.config.get("spin_wait_min", 2) + self.spin_wait_max = self.config.get("spin_wait_max", 3) def should_run(self): has_space_for_loot = inventory.Items.has_space_for_loot() @@ -115,7 +117,7 @@ def work(self): ) if 'chain_hack_sequence_number' in response_dict['responses'][ 'FORT_SEARCH']: - time.sleep(2) + action_delay(self.spin_wait_min, self.spin_wait_max) return response_dict['responses']['FORT_SEARCH'][ 'chain_hack_sequence_number'] else: @@ -132,7 +134,7 @@ def work(self): else: self.bot.fort_timeouts[fort["id"]] = (time.time() + 300) * 1000 # Don't spin for 5m return WorkerResult.ERROR - sleep(2) + action_delay(self.spin_wait_min, self.spin_wait_max) if len(forts) > 1: return WorkerResult.RUNNING diff --git a/pokemongo_bot/cell_workers/transfer_pokemon.py b/pokemongo_bot/cell_workers/transfer_pokemon.py index f9a6da6fc4..23906d6d3e 100644 --- a/pokemongo_bot/cell_workers/transfer_pokemon.py +++ b/pokemongo_bot/cell_workers/transfer_pokemon.py @@ -10,6 +10,10 @@ class TransferPokemon(BaseTask): SUPPORTED_TASK_API_VERSION = 1 + def initialize(self): + self.transfer_wait_min = self.config.get('transfer_wait_min', 1) + self.transfer_wait_max = self.config.get('transfer_wait_max', 4) + def work(self): pokemon_groups = self._release_pokemon_get_groups() for pokemon_id, group in pokemon_groups.iteritems(): @@ -164,7 +168,7 @@ def release_pokemon(self, pokemon): 'dps': pokemon.moveset.dps } ) - action_delay(self.bot.config.action_wait_min, self.bot.config.action_wait_max) + action_delay(self.transfer_wait_min, self.transfer_wait_max) def _get_release_config_for(self, pokemon): release_config = self.bot.config.release.get(pokemon) diff --git a/pokemongo_bot/cell_workers/update_live_stats.py b/pokemongo_bot/cell_workers/update_live_stats.py index e51253edc5..ecfdeea6eb 100644 --- a/pokemongo_bot/cell_workers/update_live_stats.py +++ b/pokemongo_bot/cell_workers/update_live_stats.py @@ -148,16 +148,25 @@ def _update_title(self, title, platform): :raise: RuntimeError: When the given platform isn't supported. """ - if platform == "linux" or platform == "linux2" or platform == "cygwin": - stdout.write("\x1b]2;{}\x07".format(title)) - stdout.flush() - elif platform == "darwin": - stdout.write("\033]0;{}\007".format(title)) - stdout.flush() - elif platform == "win32": - ctypes.windll.kernel32.SetConsoleTitleA(title.encode()) - else: - raise RuntimeError("unsupported platform '{}'".format(platform)) + try: + if platform == "linux" or platform == "linux2" or platform == "cygwin": + stdout.write("\x1b]2;{}\x07".format(title)) + stdout.flush() + elif platform == "darwin": + stdout.write("\033]0;{}\007".format(title)) + stdout.flush() + elif platform == "win32": + ctypes.windll.kernel32.SetConsoleTitleA(title.encode()) + else: + raise RuntimeError("unsupported platform '{}'".format(platform)) + except AttributeError: + self.emit_event( + 'log_stats', + level = 'error', + formatted = "Unable to write window title" + ) + self.terminal_title = False + self._compute_next_update() def _get_stats_line(self, player_stats): diff --git a/pokemongo_bot/cell_workers/utils.py b/pokemongo_bot/cell_workers/utils.py index 946c757b16..99fa2b23e2 100644 --- a/pokemongo_bot/cell_workers/utils.py +++ b/pokemongo_bot/cell_workers/utils.py @@ -216,8 +216,11 @@ def rad2deg(rad): def find_biggest_cluster(radius, points, order=None): graph = nx.Graph() for point in points: - if order is 'lure_info': - f = point['latitude'], point['longitude'], point['lure_info']['lure_expires_timestamp_ms'] + if order is '9QM=': + #is a lure module - 9QM= + now = int(time.time()) + remaining = now - point['last_modified_timestamp_ms'] + f = point['latitude'], point['longitude'], remaining else: f = point['latitude'], point['longitude'], 0 graph.add_node(f) diff --git a/pokemongo_bot/datastore.py b/pokemongo_bot/datastore.py new file mode 100644 index 0000000000..38f4f2c8b6 --- /dev/null +++ b/pokemongo_bot/datastore.py @@ -0,0 +1,51 @@ +""" +Use the built-in sqlite3 library as a datastore, + +To handle database migrations, use the `yoyo-migrations` package. +For further details on this, see: +https://pypi.python.org/pypi/yoyo-migrations +""" + +import inspect +import os +import sys +import warnings + +try: + from yoyo import read_migrations, get_backend +except ImportError: + warnings.warn('Please run `pip install -r requirements.txt` to ensure you have the latest required packages') + sys.exit(-1) + + +_DEFAULT = object() + +class DatabaseManager(object): + def __init__(self, bot): + self.bot = bot + + @property + def backend(self): + return get_backend('sqlite:///data/{}.db'.format(self.bot.config.username)) + + +class Datastore(object): + MIGRATIONS_PATH = _DEFAULT + + def __init__(self): + """ + When a subclass is initiated, the migrations should automatically be run. + """ + path = self.MIGRATIONS_PATH + + if path is _DEFAULT: + # `migrations` should be a sub directory of the calling package, unless a path is specified + filename = inspect.stack()[1][1] + path = os.path.join(os.path.dirname(filename), 'migrations') + elif not os.path.isdir(str(path)): + raise RuntimeError('The migrations directory does not exist') + + backend = self.database.backend + with backend.connection as conn: + migrations = read_migrations(path) + backend.apply_migrations(backend.to_apply(migrations)) diff --git a/pokemongo_bot/metrics.py b/pokemongo_bot/metrics.py index 4a6a70cae3..59652ac469 100644 --- a/pokemongo_bot/metrics.py +++ b/pokemongo_bot/metrics.py @@ -7,15 +7,15 @@ class Metrics(object): def __init__(self, bot): self.bot = bot self.start_time = time.time() - self.dust = {'start': None, 'latest': None} - self.xp = {'start': None, 'latest': None} - self.distance = {'start': None, 'latest': None} - self.encounters = {'start': None, 'latest': None} - self.throws = {'start': None, 'latest': None} - self.captures = {'start': None, 'latest': None} - self.visits = {'start': None, 'latest': None} - self.unique_mons = {'start': None, 'latest': None} - self.evolutions = {'start': None, 'latest': None} + self.dust = {'start': -1, 'latest': -1} + self.xp = {'start': -1, 'latest': -1} + self.distance = {'start': -1, 'latest': -1} + self.encounters = {'start': -1, 'latest': -1} + self.throws = {'start': -1, 'latest': -1} + self.captures = {'start': -1, 'latest': -1} + self.visits = {'start': -1, 'latest': -1} + self.unique_mons = {'start': -1, 'latest': -1} + self.evolutions = {'start': -1, 'latest': -1} self.releases = 0 self.highest_cp = {'cp': 0, 'desc': ''} @@ -84,38 +84,38 @@ def capture_stats(self): response_dict = request.call() try: self.dust['latest'] = response_dict['responses']['GET_PLAYER']['player_data']['currencies'][1]['amount'] - if self.dust['start'] is None: 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: if 'player_stats' in item['inventory_item_data']: playerdata = item['inventory_item_data']['player_stats'] self.xp['latest'] = playerdata.get('experience', 0) - if self.xp['start'] is None: self.xp['start'] = self.xp['latest'] + if self.xp['start'] < 0: self.xp['start'] = self.xp['latest'] self.visits['latest'] = playerdata.get('poke_stop_visits', 0) - if self.visits['start'] is None: self.visits['start'] = self.visits['latest'] + if self.visits['start'] < 0: self.visits['start'] = self.visits['latest'] self.captures['latest'] = playerdata.get('pokemons_captured', 0) - if self.captures['start'] is None: self.captures['start'] = self.captures['latest'] + if self.captures['start'] < 0: self.captures['start'] = self.captures['latest'] self.distance['latest'] = playerdata.get('km_walked', 0) - if self.distance['start'] is None: self.distance['start'] = self.distance['latest'] + if self.distance['start'] < 0: self.distance['start'] = self.distance['latest'] self.encounters['latest'] = playerdata.get('pokemons_encountered', 0) - if self.encounters['start'] is None: self.encounters['start'] = self.encounters['latest'] + if self.encounters['start'] < 0: self.encounters['start'] = self.encounters['latest'] self.throws['latest'] = playerdata.get('pokeballs_thrown', 0) - if self.throws['start'] is None: self.throws['start'] = self.throws['latest'] + if self.throws['start'] < 0: self.throws['start'] = self.throws['latest'] self.unique_mons['latest'] = playerdata.get('unique_pokedex_entries', 0) - if self.unique_mons['start'] is None: self.unique_mons['start'] = self.unique_mons['latest'] + if self.unique_mons['start'] < 0: self.unique_mons['start'] = self.unique_mons['latest'] self.visits['latest'] = playerdata.get('poke_stop_visits', 0) - if self.visits['start'] is None: self.visits['start'] = self.visits['latest'] + if self.visits['start'] < 0: self.visits['start'] = self.visits['latest'] self.evolutions['latest'] = playerdata.get('evolutions', 0) - if self.evolutions['start'] is None: self.evolutions['start'] = self.evolutions['latest'] + if self.evolutions['start'] < 0: self.evolutions['start'] = self.evolutions['latest'] except KeyError: # Nothing we can do if there's no player info. return diff --git a/pokemongo_bot/migrations/pokemongobot.py b/pokemongo_bot/migrations/pokemongobot.py new file mode 100644 index 0000000000..2a68265241 --- /dev/null +++ b/pokemongo_bot/migrations/pokemongobot.py @@ -0,0 +1,5 @@ +from yoyo import step + +step( + "CREATE TABLE login (timestamp INTEGER, message TEXT)", +) diff --git a/pokemongo_bot/tree_config_builder.py b/pokemongo_bot/tree_config_builder.py index 57dc9da33c..a237482fef 100644 --- a/pokemongo_bot/tree_config_builder.py +++ b/pokemongo_bot/tree_config_builder.py @@ -27,6 +27,7 @@ def _is_plugin_task(self, name): def build(self): workers = [] + deprecated_pokemon_task = False for task in self.tasks_raw: task_type = task.get('type', None) @@ -37,6 +38,16 @@ def build(self): task_config = task.get('config', {}) + if task_type in ['CatchVisiblePokemon', 'CatchLuredPokemon']: + if deprecated_pokemon_task: + continue + else: + deprecated_pokemon_task = True + task_type = 'CatchPokemon' + task_config = {} + self.bot.logger.warning('The CatchVisiblePokemon & CatchLuredPokemon tasks have been replaced with ' + 'CatchPokemon. CatchPokemon has been enabled with default settings.') + if self._is_plugin_task(task_type): worker = self.plugin_loader.get_class(task_type) else: @@ -65,4 +76,3 @@ def build(self): workers.append(instance) return workers - diff --git a/requirements.txt b/requirements.txt index 4179e1b002..adeeabab87 100644 --- a/requirements.txt +++ b/requirements.txt @@ -23,3 +23,4 @@ timeout-decorator==0.3.2 raven==5.23.0 demjson==2.2.4 greenlet==0.4.9 +yoyo-migrations==5.0.3 diff --git a/run.sh b/run.sh index 31743bb1bf..0588fcd2f7 100755 --- a/run.sh +++ b/run.sh @@ -11,12 +11,13 @@ 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) ] then -echo "Branch dev hav an update. Run ./setup.sh -u to update." +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 hav an update. Run ./setup.sh -u to update." -fi +echo "Branch master have an update. Run ./setup.sh -u to update." sleep 2 +fi if [ ! -f "$filename" ]; then echo "There's no "$filename" file. Please use ./setup.sh -c to creat one." fi diff --git a/setup.sh b/setup.sh index a013407f74..023ba4904a 100755 --- a/setup.sh +++ b/setup.sh @@ -60,8 +60,8 @@ cd $pokebotpath if [ "$(uname -s)" == "Darwin" ] then echo "You are on Mac os" -sudo brew update -sudo brew install --devel protobuf +brew update +brew install --devel protobuf elif [ $(uname -s) == CYGWIN* ] then echo "You are on Cygwin" diff --git a/tests/tree_config_builder_test.py b/tests/tree_config_builder_test.py index f8982cbfad..e8b5829d49 100644 --- a/tests/tree_config_builder_test.py +++ b/tests/tree_config_builder_test.py @@ -2,7 +2,7 @@ import json import os from pokemongo_bot import PokemonGoBot, ConfigException, MismatchTaskApiVersion, TreeConfigBuilder, PluginLoader, BaseTask -from pokemongo_bot.cell_workers import HandleSoftBan, CatchLuredPokemon +from pokemongo_bot.cell_workers import HandleSoftBan, CatchPokemon from pokemongo_bot.test.resources.plugin_fixture import FakeTask, UnsupportedApiTask def convert_from_json(str): @@ -63,7 +63,7 @@ def test_creating_two_workers(self): obj = convert_from_json("""[{ "type": "HandleSoftBan" }, { - "type": "CatchLuredPokemon" + "type": "CatchPokemon" }]""") builder = TreeConfigBuilder(self.bot, obj) @@ -71,7 +71,7 @@ def test_creating_two_workers(self): self.assertIsInstance(tree[0], HandleSoftBan) self.assertIs(tree[0].bot, self.bot) - self.assertIsInstance(tree[1], CatchLuredPokemon) + self.assertIsInstance(tree[1], CatchPokemon) self.assertIs(tree[1].bot, self.bot) def test_task_with_config(self): @@ -93,7 +93,7 @@ def test_disabling_task(self): "enabled": false } }, { - "type": "CatchLuredPokemon", + "type": "CatchPokemon", "config": { "enabled": true } @@ -103,7 +103,7 @@ def test_disabling_task(self): tree = builder.build() self.assertTrue(len(tree) == 1) - self.assertIsInstance(tree[0], CatchLuredPokemon) + self.assertIsInstance(tree[0], CatchPokemon) def test_load_plugin_task(self): package_path = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'resources', 'plugin_fixture') diff --git a/windows_bat/PokemonGo-Bot-Install.bat b/windows_bat/PokemonGo-Bot-Install.bat index dcbce98acf..99ff76ae9e 100755 --- a/windows_bat/PokemonGo-Bot-Install.bat +++ b/windows_bat/PokemonGo-Bot-Install.bat @@ -1,109 +1,269 @@ TITLE PokemonGo-Bot Installer -@ECHO OFF CLS +@ECHO OFF + + :init setlocal DisableDelayedExpansion +path c:\Program Files\Git\cmd;%PATH% +path C:\Python27;%PATH% +path C:\Python27\Scripts;%PATH% +set DownPathPython=https://www.python.org/ftp/python/2.7.12/ +set DownPathGit=https://github.com/git-for-windows/git/releases/download/v2.9.3.windows.1/ +set DownPathVisual=https://download.microsoft.com/download/7/9/6/796EF2E4-801B-4FC4-AB28-B59FBF6D907B/ +set VisualFName=VCForPython27.msi +set PythonFName86=python-2.7.12.msi +set PythonFName64=python-2.7.12.amd64.msi +set GitFName86=Git-2.9.3-32-bit.exe +set GitFName64=Git-2.9.3-64-bit.exe set "batchPath=%~0" for %%k in (%0) do set batchName=%%~nk set "vbsGetPrivileges=%temp%\OEgetPriv_%batchName%.vbs" setlocal EnableDelayedExpansion + + :checkPrivileges NET FILE 1>NUL 2>NUL if '%errorlevel%' == '0' ( goto gotPrivileges ) else ( goto getPrivileges ) + + :getPrivileges if '%1'=='ELEV' (echo ELEV & shift /1 & goto gotPrivileges) -ECHO. - -ECHO Set UAC = CreateObject^("Shell.Application"^) > "%vbsGetPrivileges%" -ECHO args = "ELEV " >> "%vbsGetPrivileges%" -ECHO For Each strArg in WScript.Arguments >> "%vbsGetPrivileges%" -ECHO args = args ^& strArg ^& " " >> "%vbsGetPrivileges%" -ECHO Next >> "%vbsGetPrivileges%" -ECHO UAC.ShellExecute "!batchPath!", args, "", "runas", 1 >> "%vbsGetPrivileges%" +@ECHO Set UAC = CreateObject^("Shell.Application"^) > "%vbsGetPrivileges%" +@ECHO args = "ELEV " >> "%vbsGetPrivileges%" +@ECHO For Each strArg in WScript.Arguments >> "%vbsGetPrivileges%" +@ECHO args = args ^& strArg ^& " " >> "%vbsGetPrivileges%" +@ECHO Next >> "%vbsGetPrivileges%" +@ECHO UAC.ShellExecute "!batchPath!", args, "", "runas", 1 >> "%vbsGetPrivileges%" "%SystemRoot%\System32\WScript.exe" "%vbsGetPrivileges%" %* exit /B + + :gotPrivileges setlocal & pushd . cd /d %~dp0 -if '%1'=='ELEV' (del "%vbsGetPrivileges%" 1>nul 2>nul & shift /1) -@ECHO ON +if '%1'=='ELEV' (del "%vbsGetPrivileges%" 1>nul 2>nul & shift /1) + + + +:detectOS +reg Query "HKLM\Hardware\Description\System\CentralProcessor\0" | find /i "x86" > NUL && set OS=32-BIT || set OS=64-BIT + + + +:CheckInstallPath +CLS +@ECHO --------------------Installation Path-------------------- +@ECHO. +@ECHO. +@ECHO. +set InstallPath= +set /p InstallPath= "Choose an installation folder or press Enter to close:" ||goto:eof +FOR /F "tokens=1-4 delims=/-. " %%G IN ("%InstallPath%") DO (set InstallPath=%%G\%%H\%%I\%%J) +set PGBotPath=%InstallPath%\PokemonGo-Bot +set DownPath=%InstallPath%\Install-Files +if not exist %DownPath% md %DownPath% +if "%~dp0"=="%PGBotPath%\windows_bat\" ( +COPY "%PGBotPath%\windows_bat\PokemonGo-Bot-Install.bat" %InstallPath% +CALL "%InstallPath%\PokemonGo-Bot-Install.bat" +exit. +) ELSE ( +@ECHO Installation Path OK! Proceeding! +) +@ECHO. @ECHO. +@ECHO. + + + +:Menu +CLS @ECHO --------------------PokemonGo-Bot Installer-------------------- -@ECHO. @ECHO. @ECHO. -@ECHO Before proceeding, please install the following software: -@ECHO. -@ECHO ---- Python 2.7.x -@ECHO http://docs.python-guide.org/en/latest/starting/installation/ @ECHO. -@ECHO ---- git -@ECHO https://git-scm.com/book/en/v2/Getting-Started-Installing-Git +if "%OS%" == "32-BIT" ( +@ECHO. 1 - Install 32-Bit software +) ELSE ( +@ECHO. 1 - Install 64-Bit software +) @ECHO. -@ECHO ---- Protoc -@ECHO https://github.com/google/protobuf/releases/download/v3.0.0-beta-4/protoc-3.0.0-beta-4-win32.zip +@ECHO. 2 - Install PokemonGo-Bot @ECHO. -@ECHO ---- Microsoft Visual C++ Compiler for Python 2.7 -@ECHO http://www.microsoft.com/en-us/download/details.aspx?id=44266 @ECHO. -@ECHO ---- encrypt.so / encrypt.dll /encrypt_64.dll (Copy to the same folder as this batch file) -@ECHO Get them from our Slack chat! https://pokemongo-bot.herokuapp.com/ @ECHO. -@ECHO ---- If you already have a config.json and a userdata.js, copy to the same folder as this batch file. + + + +:_choice +set _ok= +set /p _ok= Choose an option or press Enter to close: ||goto:eof +if "%OS%" == "32-BIT" IF "%_ok%" == "1" SET CHOICE=32Bit&GOTO :32Bit +if "%OS%" == "64-BIT" IF "%_ok%" == "1" SET CHOICE=64Bit&GOTO :64Bit +IF "%_ok%" == "2" SET CHOICE=InstallBot&GOTO :InstallBot +GOTO :_choice + + + +:32bit +CLS +@ECHO --------------------Installation of required software-------------------- @ECHO. +@ECHO Downloading Git... +if not exist %DownPath%\%GitFName86% call:getit %DownPathGit% %GitFName86% +@ECHO Downloading Python... +if not exist %DownPath%\%PythonFName86% call:getit %DownPathPython% %PythonFName86% +@ECHO Downloading Visual C++ for Python... +if not exist %DownPath%\%VisualFName% call:getit %DownPathVisual% %VisualFName% +@ECHO Installing Git... +if exist %DownPath%\%GitFName86% call:installit %GitFName86% /SILENT +@ECHO Installing Python... +if exist %DownPath%\%PythonFName86% call:installit %PythonFName86% /quiet +@ECHO Installing Visual C++ for Python... +if exist %DownPath%\%VisualFName% call:installit %VisualFName% /quiet @ECHO. @ECHO. -@PAUSE @ECHO. +@ECHO Installation of 32-Bit software is finished. @ECHO. +@ECHO Choose Install PokemonGo-Bot in next screen to complete. +@ECHO. +@ECHO Wait 5 seconds or press any key to continue... +@ECHO. +@ECHO. +@ECHO. +timeout /t 5 >nul +goto:Menu + + + +:64bit +CLS +@ECHO --------------------Installation of required software-------------------- +@ECHO. +@ECHO Downloading Git... +if not exist %DownPath%\%GitFName64% call:getit %DownPathGit% %GitFName64% +@ECHO Downloading Python... +if not exist %DownPath%\%PythonFName64% call:getit %DownPathPython% %PythonFName64% +@ECHO Downloading Visual C++ for Python... +if not exist %DownPath%\%VisualFName% call:getit %DownPathVisual% %VisualFName% +@ECHO Installing Git... +if exist %DownPath%\%GitFName64% call:installit %GitFName64% /SILENT +@ECHO Installing Python... +if exist %DownPath%\%PythonFName64% call:installit %PythonFName64% /quiet +@ECHO Installing Visual C++ for Python... +if exist %DownPath%\%VisualFName% call:installit %VisualFName% /quiet +@ECHO. +@ECHO. +@ECHO. +@ECHO Installation of 64-Bit software is finished. +@ECHO. +@ECHO Choose Install PokemonGo-Bot in next screen to complete. +@ECHO. +@ECHO Wait 5 seconds or press any key to continue... +@ECHO. +@ECHO. +@ECHO. +timeout /t 5 >nul +goto:Menu + + + +:getit +start /wait powershell -Command "Invoke-WebRequest %~1%~2 -OutFile %DownPath%\%~2" +goto:eof + + +:installit +start /wait %DownPath%\%~1 %~2 +goto:eof + + + +:InstallBot +CLS +@ECHO --------------------Creating Backup-------------------- @ECHO. -@ECHO --------------------Downloading PokemonGo-Bot-------------------- @ECHO. @ECHO. +if exist %PGBotPath%\configs\config.json copy %PGBotPath%\configs\config.json %DownPath% +if exist %PGBotPath%\web\config\userdata.js copy %PGBotPath%\web\config\userdata.js %DownPath% +if exist %PGBotPath%\encrypt. copy %PGBotPath%\encrypt. %DownPath% @ECHO. @ECHO. -RMDIR C:\Python27\PokemonGo-Bot /s /q +@ECHO. +@ECHO --------------------Downloading PokemonGo-Bot-------------------- +@ECHO. +@ECHO. +@ECHO. +if exist %PGBotPath% rmdir %PGBotPath% /s /q +if not exist %PGBotPath% md %PGBotPath% cd C:\Python27\ -pip2 install --upgrade pip pip2 install --upgrade virtualenv -pip2 install --upgrade "%~dp0\PyYAML-3.11-cp27-cp27m-win32.whl" -pip2 install --upgrade "%~dp0\PyYAML-3.11-cp27-cp27m-win_amd64.whl" -git clone --recursive -b master https://github.com/PokemonGoF/PokemonGo-Bot -pip2 install --upgrade -r C:/Python27/PokemonGo-Bot/requirements.txt -cd C:/Python27/PokemonGo-Bot/ +git clone --recursive -b master https://github.com/PokemonGoF/PokemonGo-Bot %PGBotPath% +if "%OS%" == "32-BIT" pip2 install --upgrade %PGBotPath%\windows_bat\PyYAML-3.11-cp27-cp27m-win32.whl +if "%OS%" == "64-BIT" pip2 install --upgrade %PGBotPath%\windows_bat\PyYAML-3.11-cp27-cp27m-win_amd64.whl +cd %PGBotPath% virtualenv . -call C:\Python27\PokemonGo-Bot\Scripts\activate.bat -pip2 install --upgrade -r C:/Python27/PokemonGo-Bot/requirements.txt +call "%PGBotPath%\Scripts\activate.bat" +pip2 install --upgrade -r %PGBotPath%\requirements.txt @ECHO. @ECHO. @ECHO. -@ECHO --------------------Copying additional files-------------------- +@ECHO --------------------Restoring Backup-------------------- @ECHO. @ECHO. @ECHO. -COPY "%~dp0\encrypt*.*" C:\Python27\PokemonGo-Bot\ -COPY "%~dp0\config.json" C:\Python27\PokemonGo-Bot\configs\ -COPY "%~dp0\userdata.js" C:\Python27\PokemonGo-Bot\web\config\ +if exist %DownPath%\encrypt. COPY %DownPath%\encrypt. %PGBotPath% +if exist %DownPath%\config.json COPY %DownPath%\config.json %PGBotPath%\configs\ +if exist %DownPath%\userdata.js COPY %DownPath%\userdata.js %PGBotPath%\web\config\ @ECHO. @ECHO. @ECHO. + + + +:FinalInstructions +CLS @ECHO --------------------File customization-------------------- @ECHO. @ECHO. @ECHO. +@ECHO Before starting the bot, please copy the following files to %PGBotPath% if you dont have them yet. +@ECHO. +@ECHO. +@ECHO. +@ECHO ---- encrypt.so / encrypt.dll or encrypt_64.dll +@ECHO Get them from our Slack chat! +@ECHO "https://pokemongo-bot.herokuapp.com/" +@ECHO. +@ECHO. +@ECHO. @ECHO Remember to configure both config.json and userdata.js! @ECHO. -@ECHO "C:/Python27/PokemonGo-Bot/configs/config.json" @ECHO. -@ECHO "C:/Python27/PokemonGo-Bot/web/config/userdata.js" +@ECHO. +@ECHO "%PGBotPath%/configs/config.json" +@ECHO INSTRUCTIONS: +@ECHO "https://github.com/PokemonGoF/PokemonGo-Bot/blob/master/docs/configuration_files.md" +@ECHO. +@ECHO "%PGBotPath%/web/config/userdata.js" +@ECHO INSTRUCTIONS: +@ECHO "https://github.com/PokemonGoF/PokemonGo-Bot/blob/master/docs/google_map.md" @ECHO. @ECHO To get an Google Map API Key: -@ECHO https://developers.google.com/maps/documentation/javascript/get-api-key +@ECHO "https://developers.google.com/maps/documentation/javascript/get-api-key" @ECHO. @ECHO. @ECHO. -@PAUSE \ No newline at end of file +@PAUSE + + + +:eof +ENDLOCAL +exit \ No newline at end of file diff --git a/windows_bat/PokemonGo-Bot-Repair.bat b/windows_bat/PokemonGo-Bot-Repair.bat deleted file mode 100755 index 55c9171c82..0000000000 --- a/windows_bat/PokemonGo-Bot-Repair.bat +++ /dev/null @@ -1,79 +0,0 @@ -TITLE PokemonGo-Bot Repair -@ECHO OFF -CLS - -:init -setlocal DisableDelayedExpansion -set "batchPath=%~0" -for %%k in (%0) do set batchName=%%~nk -set "vbsGetPrivileges=%temp%\OEgetPriv_%batchName%.vbs" -setlocal EnableDelayedExpansion - -:checkPrivileges -NET FILE 1>NUL 2>NUL -if '%errorlevel%' == '0' ( goto gotPrivileges ) else ( goto getPrivileges ) - -:getPrivileges -if '%1'=='ELEV' (echo ELEV & shift /1 & goto gotPrivileges) -ECHO. - -ECHO Set UAC = CreateObject^("Shell.Application"^) > "%vbsGetPrivileges%" -ECHO args = "ELEV " >> "%vbsGetPrivileges%" -ECHO For Each strArg in WScript.Arguments >> "%vbsGetPrivileges%" -ECHO args = args ^& strArg ^& " " >> "%vbsGetPrivileges%" -ECHO Next >> "%vbsGetPrivileges%" -ECHO UAC.ShellExecute "!batchPath!", args, "", "runas", 1 >> "%vbsGetPrivileges%" -"%SystemRoot%\System32\WScript.exe" "%vbsGetPrivileges%" %* -exit /B - -:gotPrivileges -setlocal & pushd . -cd /d %~dp0 -if '%1'=='ELEV' (del "%vbsGetPrivileges%" 1>nul 2>nul & shift /1) -@ECHO ON -@ECHO. -@ECHO. -@ECHO. -@ECHO --------------------Creating Backup-------------------- -@ECHO. -@ECHO. -@ECHO. -RMDIR C:\Python27\Backup /s /q -MKDIR C:\Python27\Backup -COPY C:\Python27\PokemonGo-Bot\encrypt*.* C:\Python27\Backup -COPY C:\Python27\PokemonGo-Bot\configs\config.json C:\Python27\Backup -COPY C:\Python27\PokemonGo-Bot\web\config\userdata.js C:\Python27\Backup -@ECHO. -@ECHO. -@ECHO. -@ECHO --------------------Downloading PokemonGo-Bot-------------------- -@ECHO. -@ECHO. -@ECHO. -RMDIR C:\Python27\PokemonGo-Bot /s /q -cd C:\Python27\ -pip2 install --upgrade pip -pip2 install --upgrade virtualenv -pip2 install --upgrade "%~dp0\PyYAML-3.11-cp27-cp27m-win32.whl" -pip2 install --upgrade "%~dp0\PyYAML-3.11-cp27-cp27m-win_amd64.whl" -git clone --recursive -b master https://github.com/PokemonGoF/PokemonGo-Bot -pip2 install --upgrade -r C:/Python27/PokemonGo-Bot/requirements.txt -cd C:/Python27/PokemonGo-Bot/ -virtualenv . -call C:\Python27\PokemonGo-Bot\Scripts\activate.bat -pip2 install --upgrade -r C:/Python27/PokemonGo-Bot/requirements.txt -@ECHO. -@ECHO. -@ECHO. -@ECHO --------------------Restoring Backup-------------------- -@ECHO. -@ECHO. -@ECHO. -COPY C:\Python27\Backup\encrypt*.* C:\Python27\PokemonGo-Bot\ -COPY C:\Python27\Backup\config.json C:\Python27\PokemonGo-Bot\configs\ -COPY C:\Python27\Backup\userdata.js C:\Python27\PokemonGo-Bot\web\config\ -RMDIR C:\Python27\Backup /s /q -@ECHO. -@ECHO. -@ECHO. -@PAUSE \ No newline at end of file diff --git a/windows_bat/PokemonGo-Bot-Start.bat b/windows_bat/PokemonGo-Bot-Start.bat index 37a4cf7e5f..8b7d07a0d8 100755 --- a/windows_bat/PokemonGo-Bot-Start.bat +++ b/windows_bat/PokemonGo-Bot-Start.bat @@ -1,87 +1,103 @@ TITLE PokemonGo-Bot -@ECHO OFF CLS +@ECHO OFF + + :init setlocal DisableDelayedExpansion +path c:\Program Files\Git\cmd;%PATH% +path C:\Python27;%PATH% +path C:\Python27\Scripts;%PATH% set "batchPath=%~0" for %%k in (%0) do set batchName=%%~nk set "vbsGetPrivileges=%temp%\OEgetPriv_%batchName%.vbs" setlocal EnableDelayedExpansion + + :checkPrivileges NET FILE 1>NUL 2>NUL if '%errorlevel%' == '0' ( goto gotPrivileges ) else ( goto getPrivileges ) + + :getPrivileges if '%1'=='ELEV' (echo ELEV & shift /1 & goto gotPrivileges) -ECHO. - -ECHO Set UAC = CreateObject^("Shell.Application"^) > "%vbsGetPrivileges%" -ECHO args = "ELEV " >> "%vbsGetPrivileges%" -ECHO For Each strArg in WScript.Arguments >> "%vbsGetPrivileges%" -ECHO args = args ^& strArg ^& " " >> "%vbsGetPrivileges%" -ECHO Next >> "%vbsGetPrivileges%" -ECHO UAC.ShellExecute "!batchPath!", args, "", "runas", 1 >> "%vbsGetPrivileges%" +@ECHO Set UAC = CreateObject^("Shell.Application"^) > "%vbsGetPrivileges%" +@ECHO args = "ELEV " >> "%vbsGetPrivileges%" +@ECHO For Each strArg in WScript.Arguments >> "%vbsGetPrivileges%" +@ECHO args = args ^& strArg ^& " " >> "%vbsGetPrivileges%" +@ECHO Next >> "%vbsGetPrivileges%" +@ECHO UAC.ShellExecute "!batchPath!", args, "", "runas", 1 >> "%vbsGetPrivileges%" "%SystemRoot%\System32\WScript.exe" "%vbsGetPrivileges%" %* exit /B + + :gotPrivileges setlocal & pushd . cd /d %~dp0 -if '%1'=='ELEV' (del "%vbsGetPrivileges%" 1>nul 2>nul & shift /1) -@ECHO ON -@ECHO. -@ECHO. -@ECHO. +if '%1'=='ELEV' (del "%vbsGetPrivileges%" 1>nul 2>nul & shift /1) + + + +:startBot +CLS @ECHO --------------------Verifying PokemonGo-Bot version-------------------- @ECHO. -@ECHO. -@ECHO. -cd C:/Python27/PokemonGo-Bot/ +CD.. git pull git submodule update --init --recursive -git submodule foreach git pull origin master -@ECHO. -@ECHO. @ECHO. @ECHO WARNING: Verify if the Config.json file got updated. If Yes, check if your modifications are still valid before proceeding. @ECHO. -@ECHO. -@ECHO. -@pause -@ECHO. -@ECHO. -@ECHO. +@timeout /t 10 +CLS @ECHO --------------------Initializing environment-------------------- @ECHO. -@ECHO. -@ECHO. -cd C:/Python27/PokemonGo-Bot/ virtualenv . -call C:\Python27\PokemonGo-Bot\Scripts\activate.bat -pip2 install --upgrade -r C:/Python27/PokemonGo-Bot/requirements.txt -@ECHO. -@ECHO. -@ECHO. +CD Scripts +call activate.bat +CD.. +pip2 install --upgrade -r requirements.txt +CLS @ECHO --------------------Initializing web server-------------------- @ECHO. +set BatchPath="%~dp0" +start cmd.exe /k "CD %BatchPath%&CD..&CD web&python -m SimpleHTTPServer" @ECHO. -@ECHO. -start cmd.exe /k "cd C:/Python27/PokemonGo-Bot/web&python -m SimpleHTTPServer" -@ECHO. -@ECHO. -@ECHO. +CLS @ECHO --------------------Starting bot-------------------- @ECHO. -@ECHO. -@ECHO. -python C:/Python27/PokemonGo-Bot/pokecli.py +:loop +TITLE=PokemonGo-Bot +CD %BatchPath% +CD .. +python pokecli.py +if errorlevel 1 goto restart +if errorlevel 0 goto eof + +:restart +call:problem +timeout /t 60 +goto loop +goto:eof + + + +:problem +@ECHO. +@ECHO. Something went wrong and the bot needed to be restarted. Please investigate the cause. +@ECHO. +goto:eof +:eof +exit \ No newline at end of file