From 88b400604e373ab5226453b7cc92eff00e77de39 Mon Sep 17 00:00:00 2001 From: DBa2016 Date: Tue, 30 Aug 2016 23:03:56 +0200 Subject: [PATCH 1/4] Update merge (#1) * Expand simple logging options (#4832) * Fix bot crash at start on permaban * Expanded logging options Added "logging" section to config, with options "color", "show_datetime", "show_process_name" and "show_log_level" * Added warning about deprecated logging_color arg * Display log message moved No point trying to use the logger before it's been initialised. Moved to init_config. * Remove milliseconds from datetime Because really, do we need that? * Reversed condition order for clarity First check: "if not in config", OR Second check: "is in config AND set to true" If either condition matches, the logging detail will be displayed. * Documented new log options * Modified conditions again Removed unnecessary second check for config values and slightly modified parentheses as per suggestion from @mjmadsen * Changed ) to } (#4845) Fixed an faulty character * fix incubator logic (#4848) * corrected logic to respect snipe = true * Update configuration_files.md (#4854) * corrected logic to respect snipe = true (#4855) * Revert "corrected logic to respect snipe = true" (#4857) * dont forget to update the docs when adding config changes... (#4856) * dont forget to update the docs when adding config changes... * reflect config changes.... * please keep this as is (#4859) Add stuff in the right order. * Clarify Max_distance for Sniping (#4858) * Clarify Max_distance * Added distance unit and updated configuration_files.md * - debug improvements for MoveToMap (#4860) - fix for Telegram to accept "@username" as "master", too, along with numeric IDs * Fixes catch rates. (#4863) * Implemented more granularity in the "alert_catch" parameter for Telegram alerts. * Add exceptions to json file read/writes (#4877) * Fix bot crash at start on permaban * Expanded logging options Added "logging" section to config, with options "color", "show_datetime", "show_process_name" and "show_log_level" * Added warning about deprecated logging_color arg * Display log message moved No point trying to use the logger before it's been initialised. Moved to init_config. * Remove milliseconds from datetime Because really, do we need that? * Reversed condition order for clarity First check: "if not in config", OR Second check: "is in config AND set to true" If either condition matches, the logging detail will be displayed. * Documented new log options * Modified conditions again Removed unnecessary second check for config values and slightly modified parentheses as per suggestion from @mjmadsen * Add exception handling to json file read/write ops * Removed API call in update live stats Instead of making a new api call, utilise stats already contained in metrics. * Incubate eggs fix (#4881) * Fixed incubator_eggs wrong print * Fixed pokemon hatched from eggs not added to cached inventory * Fix * Fixed not using breakable incubators * Fixed error adding pokemon to cached inventory * Moved remove egg and add Pokemon to _hatch_eggs * add some sanitycheck (#4891) * execute setup.sh -u if there is a need to (#4870) * execute setup.sh -u if there is a need to * ask the user whether to run setup.sh -u or not * fix grammatical error * Add PokemonGo bot version to docker image (#4886) * fix pep8 * Add PokemonGo bot version to docker image * Use https://api.github.com/repos/PokemonGoF/PokemonGo-Bot/commits{/sha} API * Fix remove pyc, pyo files * Call level_up_rewards on exp changes/Some pep-8 (#4896) * Call level_up_rewards on exp changes. * Cleanup * Improvements to evolve + config md updates (#4900) * Better do not evolve handling * Edit config * Update config * Edit config * Edit config * Edit config * Update config.json.path.example * Update config.json.map.example * Update config.json.example * Update config.json.cluster.example * Updated configuration_files.md * Add extra tests * Update config * Update config * Update config * Update config * Update config.json.pokemon.example * Update config.json.cluster.example * Begin fixing configuration_files.md * Small fix * Small fix * Bit for of config updated * Bit more on config * A few more to config md * Bit more of of an update * 2000 pokestop in 24h limit (#4906) * 2000 pokestop in 24h limit * 2000 pokestop in 24h limit * add config variable * config update * Update readme.md + Improve FollowPath & SleepSchedule messages (#4911) * Use logger for follow path loiter message * Update readme.md * Improve sleep message * Allow follow_path to use config's distance unit * Allow follow_path to use config's distance unit * Reduce API calls (#4916) * Fix bot crash at start on permaban * Expanded logging options Added "logging" section to config, with options "color", "show_datetime", "show_process_name" and "show_log_level" * Added warning about deprecated logging_color arg * Display log message moved No point trying to use the logger before it's been initialised. Moved to init_config. * Remove milliseconds from datetime Because really, do we need that? * Reversed condition order for clarity First check: "if not in config", OR Second check: "is in config AND set to true" If either condition matches, the logging detail will be displayed. * Documented new log options * Modified conditions again Removed unnecessary second check for config values and slightly modified parentheses as per suggestion from @mjmadsen * Add exception handling to json file read/write ops * Removed API call in update live stats Instead of making a new api call, utilise stats already contained in metrics. * Update player data in web from metrics Uses existing metrics instead of waiting on liveupdate * Implemented more granularity in the "alert_catch" parameter for Telegram alerts. * Improvements to evolve + config md updates (#4900) * Better do not evolve handling * Edit config * Update config * Edit config * Edit config * Edit config * Update config.json.path.example * Update config.json.map.example * Update config.json.example * Update config.json.cluster.example * Updated configuration_files.md * Add extra tests * Update config * Update config * Update config * Update config * Update config.json.pokemon.example * Update config.json.cluster.example * Begin fixing configuration_files.md * Small fix * Small fix * Bit for of config updated * Bit more on config * A few more to config md * Bit more of of an update * Incubate eggs fix (#4881) * Fixed incubator_eggs wrong print * Fixed pokemon hatched from eggs not added to cached inventory * Fix * Fixed not using breakable incubators * Fixed error adding pokemon to cached inventory * Moved remove egg and add Pokemon to _hatch_eggs * Call level_up_rewards on exp changes/Some pep-8 (#4896) * Call level_up_rewards on exp changes. * Cleanup * add some sanitycheck (#4891) * execute setup.sh -u if there is a need to (#4870) * execute setup.sh -u if there is a need to * ask the user whether to run setup.sh -u or not * fix grammatical error * Add PokemonGo bot version to docker image (#4886) * fix pep8 * Add PokemonGo bot version to docker image * Use https://api.github.com/repos/PokemonGoF/PokemonGo-Bot/commits{/sha} API * Fix remove pyc, pyo files * Refactoring to share inventory and reduce api calls Modifications to share cached inventory and reduce overall required api calls from 4 to 1. Only remaining api call comes from heartbeat which updates the cached inventory for sanity reasons. * Remove import of UpdateWebPlayerdata Decided there was a better way to go with this, since both UpdateWebInventory and UpdateWebPlayerdata share the same inventory input/output, just different sections. Combined into UpdateWebInventory. * Fixed conflict * Import inventory added to metrics Allows metrics to use the cached inventory to retrieve player stats instead of making another api call * Removed api call from incubate_eggs Cached inventory should be accurate enough for this * Swap auth and config position (#4909) * add telegram check messages interval (#4919) * add telegram check messages interval * config changed * fix config * telegram doc update * Add documentation (#4921) See documentation for full list of new features * documented docker for the auth.json use case (#4922) add instructions for the docker run command for the multiple config files use case. * Fix for #4718 (#4924) * Add except variable * Add except variable * Fix filter (#4925) * improve docker pull speed (#4899) * Update inventory.py (#4928) FIX: #4926 * fixed a runtime error caused by incorrect imports (#4931) * Catch exception telegram.error.NetworkError. Fixs some pylint complain. * More config parameters for MoveToMap (#4937) * fixed a runtime error caused by incorrect imports * Moving module-level constants (snipe parameters) into config file * Add experimental pokemon upgrade (power-up) logic (#4938) Add upgrade cost data file Add last pokemon level. * Set default value of skip_rounds to 30 since many people just use the default value. 30 will behave close to human. * Add exception handling for cached forts (#4943) * Add exception handling for cached forts * whitespace fix * telegram to thread * config update * doc update * update web repo to have better web ui contribute. * Using $TIMEZONE environment variable to set timezone * fix errors * fix errors * Fixing clean run issues. * During startup, no bot object. * Added option PokemonGo-Bot-Configurator Smoothed some things up Added option to run PokemonGo-Bot-Configurator at the end of the installation. * Hotfix for EvolvePokemon (#4960) * Compatiable with old protocol define in map-chat. --- .gitignore | 2 + .gitmodules | 2 +- Dockerfile | 53 +- README.md | 22 +- configs/config.json.cluster.example | 15 +- configs/config.json.example | 28 +- configs/config.json.map.example | 21 +- configs/config.json.optimizer.example | 362 ++---------- configs/config.json.path.example | 15 +- configs/config.json.pokemon.example | 15 +- data/level_to_cpm.json | 3 +- data/pokemon_upgrade_cost.json | 82 +++ docker-compose.yml | 2 + docker-compose_tor.yml | 2 + docs/configuration_files.md | 86 ++- docs/installation.md | 13 + docs/pokemon_optimizer.md | 500 +++++++++++++++++ map-chat/javascript/main.js | 37 +- pokecli.py | 57 +- pokemongo_bot/__init__.py | 100 ++-- pokemongo_bot/base_task.py | 2 +- .../cell_workers/collect_level_up_reward.py | 25 +- pokemongo_bot/cell_workers/evolve_pokemon.py | 34 +- pokemongo_bot/cell_workers/follow_path.py | 12 +- pokemongo_bot/cell_workers/incubate_eggs.py | 3 +- .../cell_workers/move_to_map_pokemon.py | 45 +- .../cell_workers/pokemon_optimizer.py | 515 +++++++++++++----- pokemongo_bot/cell_workers/spin_fort.py | 4 + pokemongo_bot/cell_workers/telegram_task.py | 94 +--- .../cell_workers/update_live_stats.py | 47 +- .../cell_workers/update_web_inventory.py | 5 +- pokemongo_bot/cell_workers/utils.py | 11 +- .../event_handlers/colored_logging_handler.py | 1 + .../event_handlers/telegram_handler.py | 107 +++- pokemongo_bot/inventory.py | 135 +++-- pokemongo_bot/metrics.py | 15 +- pokemongo_bot/sleep_schedule.py | 5 +- run.sh | 35 +- tests/inventory_test.py | 2 +- web | 2 +- windows_bat/PokemonGo-Bot-Install.bat | 225 ++++---- 41 files changed, 1741 insertions(+), 1000 deletions(-) create mode 100644 data/pokemon_upgrade_cost.json create mode 100644 docs/pokemon_optimizer.md mode change 100644 => 100755 pokemongo_bot/cell_workers/telegram_task.py mode change 100644 => 100755 pokemongo_bot/event_handlers/telegram_handler.py diff --git a/.gitignore b/.gitignore index 2f1e1c6ffd..c73247b631 100644 --- a/.gitignore +++ b/.gitignore @@ -113,7 +113,9 @@ data/map-caught-*.json data/recent-forts-*.json data/caught-*.json data/deviceid-*.txt +data/mqtt_client_id.* user_web_catchable +version # Multiple config configs/* diff --git a/.gitmodules b/.gitmodules index 612529e7ad..1d5fef9215 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "web"] path = web - url = https://github.com/OpenPoGo/OpenPoGoWeb.git \ No newline at end of file + url = https://github.com/PokemonGoF/PokemonGo-Web diff --git a/Dockerfile b/Dockerfile index 0b8b72626d..19f9ea5360 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,36 +16,43 @@ FROM alpine ARG BUILD_REPO=PokemonGoF/PokemonGo-Bot ARG BUILD_BRANCH=master -ARG TIMEZONE=Etc/UTC LABEL build_repo=$BUILD_REPO build_branch=$BUILD_BRANCH WORKDIR /usr/src/app VOLUME ["/usr/src/app/configs", "/usr/src/app/web"] -ADD https://raw.githubusercontent.com/$BUILD_REPO/$BUILD_BRANCH/requirements.txt . -RUN apk -U --no-cache add python py-pip \ - && apk --no-cache add --virtual .build-dependencies python-dev gcc make musl-dev git tzdata tar \ - && cp -fa /usr/share/zoneinfo/$TIMEZONE /etc/localtime \ - && echo $TIMEZONE > /etc/timezone \ - && ln -s locale.h /usr/include/xlocale.h \ - && pip install --no-cache-dir -r requirements.txt \ - && apk del .build-dependencies \ - && rm -rf /var/cache/apk/* /usr/include/xlocale.h \ - && find / -name '*.pyc' -o -name '*.pyo' -exec rm -f {} \; +RUN apk -U --no-cache add python py-pip tzdata \ + && rm -rf /var/cache/apk/* \ + && find / -name '*.pyc' -o -name '*.pyo' | xargs -rn1 rm -f ADD http://pgoapi.com/pgoencrypt.tar.gz /tmp/pgoencrypt.tar.gz -RUN apk --no-cache add --virtual .pgoencrypt-dependencies gcc make musl-dev tar \ - && cat /tmp/pgoencrypt.tar.gz | tar xzf - -C /tmp \ +ADD https://raw.githubusercontent.com/$BUILD_REPO/$BUILD_BRANCH/requirements.txt . +RUN apk -U --no-cache add --virtual .build-dependencies python-dev gcc make musl-dev git \ + && tar zxf /tmp/pgoencrypt.tar.gz -C /tmp \ && make -C /tmp/pgoencrypt/src \ && cp /tmp/pgoencrypt/src/libencrypt.so /usr/src/app/encrypt.so \ - && apk del .pgoencrypt-dependencies \ - && rm -rf /var/cache/apk/* /tmp/pgoencrypt /tmp/pgoencrypt.tar.gz - -ADD https://github.com/$BUILD_REPO/archive/$BUILD_BRANCH.tar.gz /tmp -RUN apk -U --no-cache add --virtual .tar-deps tar \ - && cat /tmp/$BUILD_BRANCH.tar.gz | tar -zxf - --strip-components=1 -C /usr/src/app \ - && apk del .tar-deps \ - && rm /tmp/$BUILD_BRANCH.tar.gz - -ENTRYPOINT ["python", "pokecli.py"] + && ln -s locale.h /usr/include/xlocale.h \ + && pip install --no-cache-dir -r requirements.txt \ + && apk del .build-dependencies \ + && rm -rf /var/cache/apk/* /tmp/pgoencrypt* /usr/include/xlocale.h \ + && find / -name '*.pyc' -o -name '*.pyo' | xargs -rn1 rm -f + +ADD https://api.github.com/repos/$BUILD_REPO/commits/$BUILD_BRANCH /tmp/pgobot-version +RUN apk -U --no-cache add --virtual .pgobot-dependencies wget ca-certificates tar jq \ + && wget -q -O- https://github.com/$BUILD_REPO/archive/$BUILD_BRANCH.tar.gz | tar zxf - --strip-components=1 -C /usr/src/app \ + && jq -r .sha /tmp/pgobot-version > /usr/src/app/version \ + && apk del .pgobot-dependencies \ + && rm -rf /var/cache/apk/* /tmp/pgobot-version + +RUN printf "#!/bin/sh\n\ +\n\ +TIMEZONE=\${TIMEZONE:-Etc/UTC}\n\ +\n\ +ln -sfn /usr/share/zoneinfo/\$TIMEZONE /etc/localtime\n\ +echo \$TIMEZONE > /etc/timezone\n\ +\n\ +python pokecli.py \$@\n" > /entrypoint.sh \ + && chmod +x /entrypoint.sh + +ENTRYPOINT ["/entrypoint.sh"] diff --git a/README.md b/README.md index a64e5774ea..4e62f39504 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,26 @@ # PokemonGo-Bot -PokemonGo bot is a project created by the [PokemonGoF](https://github.com/PokemonGoF) team. +[PokemonGo-Bot](https://github.com/PokemonGoF/PokemonGo-Bot) is a project created by the [PokemonGoF](https://github.com/PokemonGoF) team. ## Table of Contents - [Installation](https://github.com/PokemonGoF/PokemonGo-Bot/blob/dev/docs/installation.md) - [Documentation](https://github.com/PokemonGoF/PokemonGo-Bot/blob/dev/docs/) - [Support](#support) - - [help](#configuration-issueshelp) - - [bugs](#bugs--issues) - - [Feature request](#feature-requests) + - [Help](#configuration-issueshelp) + - [Bugs](#bugs--issues) + - [Feature Requests](#feature-requests) - [Pull Requests](#pull-requests) - [Features](#features) - [Credits](#credits) -The project is currently setup in two main branches. -- `dev` also known as `beta` - This is where the latest features are, but you may also experience some issues with stability/crashes -- `master` also known as `stable` - The bot 'should' be stable on this branch, and is generally well tested +The project is currently setup in two main branches: +- `dev` also known as `beta` - This is where the latest features are, but you may also experience some issues with stability/crashes. +- `master` also known as `stable` - The bot 'should' be stable on this branch, and is generally well tested. ## Support ### Configuration issues/help If you need any help please don't create an issue as we have a great community on Slack. You can count on the community in [#help](https://pokemongo-bot.slack.com/messages/help/) channel. - - [Click here to signup (first time only)](https://pokemongo-bot.herokuapp.com) - - [Join if you're already a member](https://pokemongo-bot.slack.com/messages/general/). + - [Click here to signup (first time only)](https://pokemongo-bot.herokuapp.com) + - [Join here if you're already a member](https://pokemongo-bot.slack.com/messages/general/) ###[Bugs / Issues](https://github.com/PokemonGoF/PokemonGo-Bot/issues?q=is%3Aissue+sort%3Aupdated-desc) If you discover a bug in the bot, please [search our issue tracker](https://github.com/PokemonGoF/PokemonGo-Bot/issues?q=is%3Aissue+sort%3Aupdated-desc) first. If it hasn't been reported, please [create a new issue](https://github.com/PokemonGoF/PokemonGo-Bot/issues/new) and ensure you follow the template guide so that our team can assist you as quickly as possible. @@ -51,10 +51,10 @@ If you'd like to make your own changes, make sure you follow the pull request te - [ ] Use candy ## Gym Battles -This bot takes a strong stance against automating gym battles. Botting gyms will have a negative effect on most players and thus the game as a whole. We will thus never accept contributions or changes containing code specific for gym battles. +[PokemonGo-Bot](https://github.com/PokemonGoF/PokemonGo-Bot) takes a strong stance against automating gym battles. Botting gyms will have a negative effect on most players and thus the game as a whole. We will thus never accept contributions or changes containing code specific for gym battles. ## Analytics -This bot is very popular and has a vibrant community. Because of that, it has become very difficult for us to know how the bot is used and what errors people hit. By capturing small amounts of data, we can prioritize our work better such as fixing errors that happen to a large percentage of our user base, not just a vocal minority. +[PokemonGo-Bot](https://github.com/PokemonGoF/PokemonGo-Bot) is very popular and has a vibrant community. Because of that, it has become very difficult for us to know how the bot is used and what errors people hit. By capturing small amounts of data, we can prioritize our work better such as fixing errors that happen to a large percentage of our user base, not just a vocal minority. Our goal is to help inform our decisions by capturing data that helps us get aggregate usage and error reports, not personal information. To view the code that handles analytics in our master branch, you can use this [search link](https://github.com/PokemonGoF/PokemonGo-Bot/search?utf8=%E2%9C%93&q=BotEvent). diff --git a/configs/config.json.cluster.example b/configs/config.json.cluster.example index ed7d9e6508..8ee264c2ce 100644 --- a/configs/config.json.cluster.example +++ b/configs/config.json.cluster.example @@ -95,9 +95,18 @@ "type": "EvolvePokemon", "config": { "enabled": false, - "//evolve all except Zubat and Rattata": "", - "//evolve_all": "-Zubat,-Rattata", - "evolve_all": "none", + + "// evolve only pidgey and drowzee": "", + "// evolve_list": "pidgey, drowzee", + "// donot_evolve_list": "none", + + "// evolve all but pidgey and drowzee": "", + "// evolve_list": "all", + "// donot_evolve_list": "pidgey, drowzee", + + "evolve_list": "all", + "donot_evolve_list": "none", + "first_evolve_by": "cp", "evolve_above_cp": 500, "evolve_above_iv": 0.8, diff --git a/configs/config.json.example b/configs/config.json.example index 5cb0d9c2d9..e7a6244691 100644 --- a/configs/config.json.example +++ b/configs/config.json.example @@ -13,11 +13,11 @@ "enabled": false, "master": null, "// old syntax, still supported: alert_catch": ["all"], - "// new syntax:": {}, - "alert_catch": { - "all": {"operator": "and", "cp": 1300, "iv": 0.95}, - "Snorlax": {"operator": "or", "cp": 900, "iv": 0.9} - } + "// new syntax:": {}, + "alert_catch": { + "all": {"operator": "and", "cp": 1300, "iv": 0.95}, + "Snorlax": {"operator": "or", "cp": 900, "iv": 0.9} + } } }, { @@ -120,9 +120,18 @@ "type": "EvolvePokemon", "config": { "enabled": false, - "//evolve all except Zubat and Rattata": "", - "//evolve_all": "-Zubat,-Rattata", - "evolve_all": "none", + + "// evolve only pidgey and drowzee": "", + "// evolve_list": "pidgey, drowzee", + "// donot_evolve_list": "none", + + "// evolve all but pidgey and drowzee": "", + "// evolve_list": "all", + "// donot_evolve_list": "pidgey, drowzee", + + "evolve_list": "all", + "donot_evolve_list": "none", + "first_evolve_by": "cp", "evolve_above_cp": 500, "evolve_above_iv": 0.8, @@ -194,7 +203,8 @@ "config": { "enabled": true, "spin_wait_min": 3, - "spin_wait_max": 5 + "spin_wait_max": 5, + "daily_spin_limit": 1900 } }, { "type": "UpdateWebInventory", diff --git a/configs/config.json.map.example b/configs/config.json.map.example index ee0d99834a..050dc332de 100644 --- a/configs/config.json.map.example +++ b/configs/config.json.map.example @@ -95,9 +95,18 @@ "type": "EvolvePokemon", "config": { "enabled": false, - "//evolve all except Zubat and Rattata": "", - "//evolve_all": "-Zubat,-Rattata", - "evolve_all": "none", + + "// evolve only pidgey and drowzee": "", + "// evolve_list": "pidgey, drowzee", + "// donot_evolve_list": "none", + + "// evolve all but pidgey and drowzee": "", + "// evolve_list": "all", + "// donot_evolve_list": "pidgey, drowzee", + + "evolve_list": "all", + "donot_evolve_list": "none", + "first_evolve_by": "cp", "evolve_above_cp": 500, "evolve_above_iv": 0.8, @@ -192,6 +201,12 @@ "map_path": "raw_data", "walker": "StepWalker", "max_extra_dist_fort": 10, + "skip_rounds": 5, + "update_map_min_distance_meters": 500, + "update_map_min_time_sec": 120, + "snipe_sleep_sec": 2, + "snipe_max_in_chain": 2, + "debug": false, "catch": { "==========Legendaries==========": 0, "Aerodactyl": 1000, diff --git a/configs/config.json.optimizer.example b/configs/config.json.optimizer.example index 0f1da751d3..c08bed77c2 100644 --- a/configs/config.json.optimizer.example +++ b/configs/config.json.optimizer.example @@ -1,312 +1,54 @@ { - "websocket_server": false, - "heartbeat_threshold": 10, - "enable_social": true, - "live_config_update": { - "enabled": false, - "tasks_only": false - }, - "tasks": [ - { - "//NOTE: This task MUST be placed on the top of task list": {}, - "type": "RandomAlivePause", - "config": { - "enabled": false, - "min_duration": "00:00:10", - "max_duration": "00:10:00", - "min_interval": "00:05:00", - "max_interval": "01:30:00" - } - }, - { - "type": "HandleSoftBan" - }, - { - "type": "CompleteTutorial", - "config": { - "enabled": false, - "// set a name": "", - "nickname": "" - } - }, - { - "type": "CollectLevelUpReward", - "config": { - "collect_reward": true, - "level_limit": -1 - } - }, - { - "type": "IncubateEggs", - "config": { - "enabled": true, - "infinite_longer_eggs_first": false, - "breakable_longer_eggs_first": true, - "min_interval": 120 - } - }, - { - "type": "UpdateLiveStats", - "config": { - "enabled": false, - "min_interval": 10, - "stats": ["username", "uptime", "stardust_earned", "xp_earned", "xp_per_hour", "stops_visited"], - "terminal_log": true, - "terminal_title": true - } - }, - { - "type": "UpdateLiveInventory", - "config": { - "enabled": false, - "min_interval": 120, - "show_all_multiple_lines": false, - "items": ["pokemon_bag", "space_info", "pokeballs", "greatballs", "ultraballs", "razzberries", "luckyegg"] - } - }, - { - "type": "ShowBestPokemon", - "config": { - "enabled": true, - "min_interval": 60, - "amount": 5, - "order_by": "cp", - "info_to_show": ["cp", "ivcp", "dps", "hp"] - } - }, - { - "type": "PokemonOptimizer", - "config": { - "enabled": true, - "// the 'transfer' parameter activate or deactivate the transfer of pokemons": {}, - "// at false, no pokemon is going to be transfered, ever": {}, - "// at false, you will still get the log information of what the optimizer": {}, - "// would have transfered if the parameter was true": {}, - "transfer": true, - "// 'transfer_wait_min' and 'transfer_wait_max' are the minimum and maximum": {}, - "// time to wait when transferring a pokemon": {}, - "transfer_wait_min": 3, - "transfer_wait_max": 5, - "// the 'evolve' parameter activate or deactivate the evolution of pokemons": {}, - "// at false, no pokemon is going to be evolved, ever": {}, - "// at false, you will still get the log information of what the": {}, - "// optimizer would have evolved if the parameter was true": {}, - "evolve": true, - "// Time in seconds to wait between two evolve": {}, - "evolve_time": 20, - "// the 'evolve_for_xp' parameter let you choose if you want the optimizer": {}, - "// to use your candies to evolve low quality pokemons in order to maximize your xp": {}, - "// at false, the optimizer will still use candies to evolve your best Pokemons": {}, - "evolve_for_xp": true, - "// the 'evolve_only_with_lucky_egg' parameter let you choose if you want the optimizer": {}, - "// to only Evolve Pokemons when a lucky egg is available": {}, - "evolve_only_with_lucky_egg": false, - "// the 'evolve_count_for_lucky_egg' parameter let you define the minimum": {}, - "// number of Pokemons that must evolve before using a lucky egg": {}, - "// If that number is not reached, and evolve_only_with_lucky_egg is true, evolution will be skipped": {}, - "// If that number is not reached, and evolve_only_with_lucky_egg is false,": {}, - "// evolution will be performed without using a lucky egg": {}, - "evolve_count_for_lucky_egg": 92, - "// the 'may_use_lucky_egg' parameter let you choose if you want the optimizer": {}, - "// to use a lucky egg right before evolving Pokemons. At false; the optimizer": {}, - "// is free to evolve Pokemons even if you do not have any lucky egg.": {}, - "may_use_lucky_egg": true, - "// the 'keep' parameter let you define what pokemons you consider are the 'best'. These Pokemons": {}, - "// will be keep and evolved. Note that Pokemons are evaluated inside their whole family": {}, - "// Multiple way of ranking can be defined. Following configuration let you keep the best iv,": {}, - "// the best ncp and the best cp": {}, - "keep": [ - { - "// Following setting let you keep the best iv of the family": {}, - "// the 'top' parameter allow you to define how many Pokemons you want to keep": {}, - "// at the top of your ranking. If several Pokemons get the same score, they are": {}, - "// considered equal. Thus, top=1 might result in keeping more than 1 Pokemon.": {}, - "top": 1, - "// the 'evolve' parameter let you choose if you want to evolve the Pokemons you keep": {}, - "evolve": true, - "// the 'sort' parameter define how you want to rank your pokemons": {}, - "// Critera are sorted fro, the most important to the least important.": {}, - "// Available criteria are:": {}, - "// 'iv' = individual value": {}, - "// 'ivcp' = iv weigted so that for equal iv, attack > defense > stamina": {}, - "// 'cp' = combat power (can be increased with candies)": {}, - "// 'cp_exact' = combat power (not rounded)": {}, - "// 'ncp' (normalized cp) or 'cp_percent' = ratio cp / max_cp": {}, - "// 'iv_attack' = attach component of iv": {}, - "// 'iv_defense' = defense component of iv": {}, - "// 'iv_stamina' = stamina component of iv": {}, - "// 'dps' = raw dps based on the moves of the pokemon": {}, - "// 'dps_attack' = average dps when attacking": {}, - "// 'attack_perfection' = ratio dps_attack / best_dps_attack. Return same order as 'dps_attack'": {}, - "// 'dps_defense' = average dps when defending": {}, - "// 'defense_perfection' = ratio dps_defense / best_dps_defense. Return same order as 'dps_defense'": {}, - "// 'hp' = current health points": {}, - "// 'hp_max' = max health points": {}, - "// Note that the more criteria you add to this list, the less likely Pokemons": {}, - "// will be equals": {}, - "sort": ["iv"] - }, - { - "// Following setting let you keep keep the best normalized cp of the family": {}, - "// That is the Pokemon with higher CP once fully evolved": {}, - "top": 1, - "evolve": true, - "sort": ["ncp"] - }, - { - "// Following setting let you keep keep the best cp of the family.": {}, - "// But will not evolve it further (in favor of the best ncp)": {}, - "// It will only applies to the family of 'Dragonite', 'Arcanine' and 'Muk'": {}, - "// Other families are not following this setting": {}, - "names": ["Dragonite", "Arcanine", "Muk"], - "top": 1, - "evolve": false, - "sort": ["cp"] - } - ] - } - }, - { - "type": "RecycleItems", - "config": { - "enabled": true, - "min_empty_space": 15, - "max_balls_keep": 150, - "max_potions_keep": 50, - "max_berries_keep": 70, - "max_revives_keep": 70, - "item_filter": { - "Pokeball": { "keep" : 100 }, - "Potion": { "keep" : 10 }, - "Super Potion": { "keep" : 20 }, - "Hyper Potion": { "keep" : 30 }, - "Revive": { "keep" : 30 }, - "Razz Berry": { "keep" : 100 } - }, - "recycle_wait_min": 3, - "recycle_wait_max": 5, - "recycle_force": true, - "recycle_force_min": "00:01:00", - "recycle_force_max": "00:05:00" - } - }, - { - "type": "CatchPokemon", - "config": { - "enabled": true, - "catch_visible_pokemon": true, - "catch_lured_pokemon": true, - "min_ultraball_to_keep": 5, - "berry_threshold": 0.35, - "vip_berry_threshold": 0.9, - "treat_unseen_as_vip": true, - "daily_catch_limit": 800, - "catch_throw_parameters": { - "excellent_rate": 0.1, - "great_rate": 0.5, - "nice_rate": 0.3, - "normal_rate": 0.1, - "spin_success_rate" : 0.6 - }, - "catch_simulation": { - "flee_count": 3, - "flee_duration": 2, - "catch_wait_min": 3, - "catch_wait_max": 6, - "berry_wait_min": 3, - "berry_wait_max": 5, - "changeball_wait_min": 3, - "changeball_wait_max": 5, - "newtodex_wait_min": 20, - "newtodex_wait_max": 30 - } - } - }, - { - "type": "SpinFort", - "config": { - "enabled": true, - "spin_wait_min": 3, - "spin_wait_max": 5 - } - }, - { "type": "UpdateWebInventory", - "config": { - "enabled": true - } - }, - { - "type": "MoveToFort", - "config": { - "enabled": true, - "lure_attraction": true, - "lure_max_distance": 2000, - "log_interval": 5 - } - }, - { - "type": "FollowSpiral", - "config": { - "enabled": true, - "diameter": 4, - "step_size": 70 - } - } - ], - "map_object_cache_time": 5, - "forts": { - "avoid_circles": true, - "max_circle_size": 50, - "cache_recent_forts": true - }, - "pokemon_bag": { - "// if 'show_at_start' is true, it will log all the pokemons in the bag (not eggs) at bot start": {}, - "show_at_start": true, - "// if 'show_count' is true, it will show the amount of each pokemon (minimum 1)": {}, - "show_count": false, - "// if 'show_candies' is true, it will show the amount of candies for each pokemon": {}, - "show_candies": false, - "// 'pokemon_info' parameter define which info to show for each pokemon": {}, - "// the available options are": {}, - "// ['cp', 'iv_ads', 'iv_pct', 'ivcp', 'ncp', 'level', 'hp', 'moveset', 'dps']": {}, - "pokemon_info": ["cp", "iv_pct"] - }, - "walk_max": 4.16, - "walk_min": 2.16, - "alt_min": 500, - "alt_max": 1000, - "sleep_schedule": [ - { - "time": "12:00", - "duration": "5:30", - "time_random_offset": "00:30", - "duration_random_offset": "00:30", - "wake_up_at_location": "" - }, - { - "time": "17:45", - "duration": "3:00", - "time_random_offset": "01:00", - "duration_random_offset": "00:30", - "wake_up_at_location": "" - } - ], - "debug": false, - "test": false, - "walker_limit_output": false, - "health_record": true, - "location_cache": true, - "distance_unit": "km", - "reconnecting_timeout": 15, - "logging": { - "color": true, - "show_datetime": true, - "show_process_name": true, - "show_log_level": true - }, - "catch": { - "any": { - "always_catch": true - } - } + "tasks": [ + { + "type": "PokemonOptimizer", + "config": { + "enabled": true, + "transfer": true, + "transfer_wait_min": 3, + "transfer_wait_max": 5, + "evolve": true, + "evolve_time": 25, + "evolve_for_xp": true, + "evolve_only_with_lucky_egg": false, + "evolve_count_for_lucky_egg": 80, + "may_use_lucky_egg": true, + "upgrade": true, + "groups": { + "gym": ["Dragonite", "Snorlax", "Lapras", "Arcanine"] + }, + "keep": [ + { + "mode": "by_family", + "top": 1, + "sort": [{"iv": 0.9}], + "evolve": true, + "upgrade": false + }, + { + "mode": "by_family", + "top": 1, + "sort": [{"ncp": "0.9"}], + "evolve": true, + "upgrade": false + }, + { + "mode": "by_family", + "top": 1, + "sort": ["cp"], + "evolve": false, + "upgrade": false + }, + { + "mode": "by_family", + "names": ["gym"], + "top": 3, + "sort": [{"iv": 0.9}, {"ncp": "0.9"}], + "evolve": true, + "upgrade": true + } + ] + } + } + ] } diff --git a/configs/config.json.path.example b/configs/config.json.path.example index 2e053d6d8d..efa9493505 100644 --- a/configs/config.json.path.example +++ b/configs/config.json.path.example @@ -95,9 +95,18 @@ "type": "EvolvePokemon", "config": { "enabled": false, - "//evolve all except Zubat and Rattata": "", - "//evolve_all": "-Zubat,-Rattata", - "evolve_all": "none", + + "// evolve only pidgey and drowzee": "", + "// evolve_list": "pidgey, drowzee", + "// donot_evolve_list": "none", + + "// evolve all but pidgey and drowzee": "", + "// evolve_list": "all", + "// donot_evolve_list": "pidgey, drowzee", + + "evolve_list": "all", + "donot_evolve_list": "none", + "first_evolve_by": "cp", "evolve_above_cp": 500, "evolve_above_iv": 0.8, diff --git a/configs/config.json.pokemon.example b/configs/config.json.pokemon.example index 37d3cf98e7..f2641b12ac 100644 --- a/configs/config.json.pokemon.example +++ b/configs/config.json.pokemon.example @@ -95,9 +95,18 @@ "type": "EvolvePokemon", "config": { "enabled": false, - "//evolve all except Zubat and Rattata": "", - "//evolve_all": "-Zubat,-Rattata", - "evolve_all": "none", + + "// evolve only pidgey and drowzee": "", + "// evolve_list": "pidgey, drowzee", + "// donot_evolve_list": "none", + + "// evolve all but pidgey and drowzee": "", + "// evolve_list": "all", + "// donot_evolve_list": "pidgey, drowzee", + + "evolve_list": "all", + "donot_evolve_list": "none", + "first_evolve_by": "cp", "evolve_above_cp": 500, "evolve_above_iv": 0.8, diff --git a/data/level_to_cpm.json b/data/level_to_cpm.json index d2483d9a41..be0d57b3f9 100644 --- a/data/level_to_cpm.json +++ b/data/level_to_cpm.json @@ -77,5 +77,6 @@ "38.5": 0.781790055, "39": 0.78463697, "39.5": 0.787473578, - "40": 0.79030001 + "40": 0.79030001, + "40.5": 0.7931163849 } \ No newline at end of file diff --git a/data/pokemon_upgrade_cost.json b/data/pokemon_upgrade_cost.json new file mode 100644 index 0000000000..06860e57ae --- /dev/null +++ b/data/pokemon_upgrade_cost.json @@ -0,0 +1,82 @@ +[ +[1, 200], +[1, 200], +[1, 200], +[1, 200], +[1, 400], +[1, 400], +[1, 400], +[1, 400], +[1, 600], +[1, 600], +[1, 600], +[1, 600], +[1, 800], +[1, 800], +[1, 800], +[1, 800], +[1, 1000], +[1, 1000], +[1, 1000], +[1, 1000], +[2, 1300], +[2, 1300], +[2, 1300], +[2, 1300], +[2, 1600], +[2, 1600], +[2, 1600], +[2, 1600], +[2, 1900], +[2, 1900], +[2, 1900], +[2, 1900], +[2, 2200], +[2, 2200], +[2, 2200], +[2, 2200], +[2, 2500], +[2, 2500], +[2, 2500], +[2, 2500], +[3, 3000], +[3, 3000], +[3, 3000], +[3, 3000], +[3, 3000], +[3, 3500], +[3, 3500], +[3, 3500], +[4, 4000], +[4, 4000], +[4, 4000], +[4, 4000], +[4, 4500], +[4, 4500], +[4, 4500], +[4, 4500], +[4, 5000], +[4, 5000], +[4, 5000], +[4, 5000], +[6, 6000], +[6, 6000], +[6, 6000], +[6, 6000], +[8, 7000], +[8, 7000], +[8, 7000], +[8, 7000], +[10, 8000], +[10, 8000], +[10, 8000], +[10, 8000], +[12, 9000], +[12, 9000], +[12, 9000], +[12, 9000], +[15, 10000], +[15, 10000], +[15, 10000], +[15, 10000] +] diff --git a/docker-compose.yml b/docker-compose.yml index 291b692939..8625631650 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,6 +6,8 @@ services: - ./configs:/usr/src/app/configs - ./data:/usr/src/app/data - ./web:/usr/src/app/web + environment: + - TIMEZONE=Etc/UTC stdin_open: true tty: true bot1-pokegoweb: diff --git a/docker-compose_tor.yml b/docker-compose_tor.yml index bb5309e826..33b9acbfc3 100644 --- a/docker-compose_tor.yml +++ b/docker-compose_tor.yml @@ -23,6 +23,8 @@ services: - ./configs:/usr/src/app/configs - ./data:/usr/src/app/data - ./web:/usr/src/app/web + environment: + - TIMEZONE=Etc/UTC stdin_open: true tty: true bot1-pokegoweb: diff --git a/docs/configuration_files.md b/docs/configuration_files.md index d56b213235..d6828229d4 100644 --- a/docs/configuration_files.md +++ b/docs/configuration_files.md @@ -55,9 +55,10 @@ Document the configuration options of PokemonGo-Bot. ## Usage [[back to top](#table-of-contents)] -1. copy `config.json.example` to `config.json`. -2. Edit `config.json` and replace `auth_service`, `username`, `password`, `location` and `gmapkey` with your parameters (other keys are optional, check `Advance Configuration` below) -3. Simply launch the script with : `./run.sh` or `./pokecli.py` or `python pokecli.py -cf ./configs/config.json` if you want to specify a config file +1. copy `auth.json.example` to `auth.json`. +2. Edit `auth.json` and replace `auth_service`, `username`, `password`, `location` and `gmapkey` with your parameters (other keys are optional) +3. copy `config.json.example` to `config.json`.= +3. Simply launch the script with : `./run.sh` or './run.sh ./configs/your_auth_file.json ./configs/your_base_config_file.json' ## Advanced Configuration @@ -105,27 +106,83 @@ The behaviors of the bot are configured via the `tasks` key in the `config.json` ### Task Options: [[back to top](#table-of-contents)] * CatchPokemon - * `treat_unseen_as_vip`: Default `"true"` | Set to `"false"` to disable treating pokemons you don't have in your pokedex as VIPs. + * `enabled`: Default "true" | Enable/Disable the task. + * `treat_unseen_as_vip`: Default `"true"` | If true, treat new to dex as VIP + * `catch_visible_pokemon`: Default "true" | If enabled, attempts to catch "visible" pokemon that are reachable + * `catch_lured_pokemon`: Default "true" | If enabled, attempts to catch "lured" pokemon that are reachable + * `min_ultraball_to_keep`: Default 5 | Minimum amount of reserved ultraballs to have on hand (for VIP) + * `berry_threshold`: Default 0.35 | Catch percentage we start throwing berries + * `vip_berry_threshold`: Default 0.9 | Something similar? + * `treat_unseen_as_vip`: Default "true" | If enabled, treat new to our dex as VIP + * `daily_catch_limit`: Default 800 | How many pokemon we limit ourselves to daily + * `catch_throw_parameters`: Variable catch settings + * `excellent_rate`: 0.1 | Change of excellent throw + * `great_rate`: 0.5 | Change of excellent throw + * `nice_rate`: 0.3 | Change of nice throw + * `normal_rate`: 0.1 | Change of normal throw + * `spin_success_rate` : 0.6 | Change of using a spin throw + * `hit_rate`: 0.75 | Change of overall hit chance + `catch_simulation`: + * `flee_count`: 3 | ?? + * `flee_duration`: 2 | ?? + * `catch_wait_min`: 3 | Minimum time to wait after a catch + * `catch_wait_max`: 6 | Maximum time to wait after a catch + * `berry_wait_min`: 3 | Minimum time to wait after throwing berry + * `berry_wait_max`: 5 | Maxiumum time to wait after throwing berry + * `changeball_wait_min`: 3 | Minimum time to wait when changing balls + * `changeball_wait_max`: 5 | Maximum time to wait when changing balls + * `newtodex_wait_min`: 20 | Minimum time to wait if we caught a new type of pokemon + * `newtodex_wait_max`: 39 | Maximum time to wait if we caught a new type of pokemon * EvolvePokemon - * `evolve_all`: Default `NONE` | Set to `"all"` to evolve Pokémon if possible when the bot starts. Can also be set to individual Pokémon as well as multiple separated by a comma. e.g "Pidgey,Rattata,Weedle,Zubat" + * `enable`: Disable or enable this task. + * `evolve_all`: Default `NONE` | Depreciated. Please use evolve_list and donot_evolve_list + * `evolve_list`: Default `all` | Set to all, or specifiy different pokemon seperated by a comma + * `donot_evolve_list`: Default `none` | Pokemon seperated by comma, will be ignored from evolve_list * `min_evolve_speed`: Default `25` | Minimum seconds to wait between each evolution * `max_evolve_speed`: Default `30` | Maximum seconds to wait between each evolution - * `use_lucky_egg`: Default: `False` + * `use_lucky_egg`: Default: `False` | Only evolve if we can use a lucky egg * FollowPath + * `enable`: Disable or enable this task. * `path_mode`: Default `loop` | Set the mode for the path navigator (loop, linear or single). * `path_file`: Default `NONE` | Set the file containing the waypoints for the path navigator. * FollowSpiral + * `enable`: Disable or enable this task. + * `spin_wait_min`: Default 3 | Minimum wait time after fort spin + * `spin_wait_max`: Default 5 | Maximum wait time after fort spin * HandleSoftBan * IncubateEggs - * `longer_eggs_first`: Default `True` + * `enable`: Disable or enable this task. + * `longer_eggs_first`: Depreciated + * `infinite_longer_eggs_first`: Default `true` | Prioritize longer eggs in perminent incubators. + * `breakable_longer_eggs_first`: Default `false` | Prioritize longer eggs in breakable incubators. + * `min_interval`: Default `120` | Minimum number of seconds between incubation updates. + * `infinite`: Default `[2,5,10]` | Types of eggs to be incubated in permanent incubators. + * `breakable`: Default `[2,5,10]` | Types of eggs to be incubated in breakable incubators. * MoveToFort + * `enable`: Disable or enable this task. + * `lure_attraction`: Default `true` | Be more attracted to lured forts than non + * `lure_max_distance`: Default `2000` | Maxmimum distance lured forts influence this task + * `walker`: Default `StepWalker` | Which walker moves us + * `log_interval`: Default `5` | Log output interval * [MoveToMapPokemon](#sniping-movetolocation) * NicknamePokemon + * `enable`: Disable or enable this task. * `nickname_template`: Default `""` | See the [Pokemon Nicknaming](#pokemon-nicknaming) section for more details * `nickname_above_iv`: Default `0` | Rename pokemon which iv is highter than the value * `dont_nickname_favorite`: Default `false` | Prevents renaming of favorited pokemons * `good_attack_threshold`: Default `0.7` | Threshold for perfection of the attack in it's type *(0.0-1.0)* after which attack will be treated as good.
Used for `{fast_attack_char}`, `{charged_attack_char}`, `{attack_code}` templates * RecycleItems + * `enabled`: Default `true` | Disable or enable this task + * `min_empty_space`: Default 15 | minimum spaces before forcing transfer + * `max_balls_keep`: Default 150 | Maximum cumlative balls to keep + * `max_potions_keep`: Default 50 | Maximum cumlative potions to keep + * `max_berries_keep`: Default 70 | Maximum culative berries to keep + * `max_revives_keep`: Default 70 | Maxiumum culative revies to keep + * `recycle_wait_min`: 3 | Minimum wait time after recycling an item + * `recycle_wait_max`: 5 | Maxiumum culative revies to keep + * `recycle_force`: Default true | Enable/Disable time forced item recycling + * `recycle_force_min`: Default `00:01:00` | Minimum time to wait before forcing recycling + * `recycle_force_max`: default `00:05:00` | Maximum time to wait before forcing recycling > **NOTE:** It's highly recommended to put this task before MoveToFort and SpinFort tasks. This way you'll most likely be able to loot. * `min_empty_space`: Default `6` | Minimum empty space to keep in inventory. Once the inventory has less empty space than that amount, the recycling process is triggered. Set it to the inventory size to trigger it at every tick. @@ -139,7 +196,11 @@ The behaviors of the bot are configured via the `tasks` key in the `config.json` * `recycle_force_max`: Default `00:10:00` | Maximum time to wait before scheduling next forced recycle * SpinFort + * `enabled`: Default true | Enable for disable this task + * `spin_wait_min`: Defaut 3 | Minimum wait after spinning a fort + * `spin_wait_max`: Default 5 | Maximum wait after spinning a fort * TransferPokemon + * `enable`: Disable or enable this task. * `min_free_slot`: Default `5` | Once the pokebag has less empty slots than this amount, the transfer process is triggered. | Big values (i.e 9999) will trigger the transfer process after each catch. * UpdateLiveStats * [UpdateLiveInventory](#updateliveinventory-settings) @@ -928,8 +989,8 @@ Bot answer on command '/info' self stats. ### Options * `telegram_token` : bot token (getting [there](https://core.telegram.org/bots#6-botfather) - one token per bot) -* `master` : id (without quotes) or username (in quotes, first character @) of bot owner, who will gett announces. -* `alert_catch` : array of pokemons, which will be announced on catch. if first array item `all` - announce all pokemons. +* `master` : id (without quotes) of bot owner, who will gett announces. +* `alert_catch` : dict of rules pokemons catch. ### Sample configuration [[back to top](#table-of-contents)] @@ -939,9 +1000,10 @@ Bot answer on command '/info' self stats. "config": { "enabled": true, "master": 12345678, - "//master": "@username", - "alert_catch": ["Lapras","Dragonite"], - "//alert_catch": ["all"] + "alert_catch": { + "all": {"operator": "and", "cp": 1300, "iv": 0.95}, + "Snorlax": {"operator": "or", "cp": 900, "iv": 0.9} + } } } ``` diff --git a/docs/installation.md b/docs/installation.md index 36424a0405..3919924584 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -83,6 +83,19 @@ To run the bot container with the PokemonGo-Bot Docker image you've created: docker run --name=bot1-pokego --rm -it -v $(pwd)/configs/config.json:/usr/src/app/configs/config.json pokemongo-bot ``` +>In the case you configured authentification to be handled by auth.json file make sure you mount that file as a volume also + +>``` +docker run --name=bot1-pokego --rm -it -v $(pwd)/configs/auth.json:/usr/src/app/configs/auth.json -v $(pwd)/configs/config.json:/usr/src/app/configs/config.json -v $(pwd)/web/:/usr/src/app/web/ pokemongo-bot +``` + +>or for a simplified version mount your whole configs/ subdir to /usr/src/app/configs + +>``` +docker run --name=bot1-pokego --rm -it -v $(pwd)/configs:/usr/src/app/configs -v $(pwd)/web/:/usr/src/app/web/ pokemongo-bot +``` + + Run a second container provided with the OpenPoGoBotWeb view: ``` diff --git a/docs/pokemon_optimizer.md b/docs/pokemon_optimizer.md new file mode 100644 index 0000000000..0752cf1855 --- /dev/null +++ b/docs/pokemon_optimizer.md @@ -0,0 +1,500 @@ +# Pokemon Optimizer +- [About](#about) +- [Configuration](#configuration) + - [Default configuration](#default-configuration) + - [Understand parameters](#understand-parameters) + - [enabled](#enabled) + - [transfer](#transfer) + - [transfer_wait_min](#transfer_wait_min) + - [transfer_wait_max](#transfer_wait_max) + - [evolve](#evolve) + - [evolve_time](#evolve_time) + - [evolve_for_xp](#evolve_for_xp) + - [evolve_only_with_lucky_egg](#evolve_only_with_lucky_egg) + - [evolve_count_for_lucky_egg](#evolve_count_for_lucky_egg) + - [may_use_lucky_egg](#may_use_lucky_egg) + - [upgrade](#upgrade) + - [groups](#groups) + - [keep](#keep) + - [mode](#keep-mode) + - [names](#keep-names) + - [top](#keep-top) + - [sort](#keep-sort) + - [evolve](#keep-evolve) + - [Examples of configuration](#examples-of-configuration) +- [Eevee case](#eevee-case) + +# About +The Pokemon Optimizer manage transfer and evolution of your Pokemon. +
It can replace or complement the classical Evolve and Transfer tasks. +
It will be triggered when you bag of Pokemon is full and has no effect until it happens. + +[[back to top](#pokemon-optimizer)] + +# Configuration +## Default configuration +``` +{ + "tasks": [ + { + "type": "PokemonOptimizer", + "config": { + "enabled": true, + "transfer": true, + "transfer_wait_min": 3, + "transfer_wait_max": 5, + "evolve": true, + "evolve_time": 25, + "evolve_for_xp": true, + "evolve_only_with_lucky_egg": false, + "evolve_count_for_lucky_egg": 80, + "may_use_lucky_egg": true, + "upgrade": true, + "groups": { + "gym": ["Dragonite", "Snorlax", "Lapras", "Arcanine"] + }, + "keep": [ + { + "mode": "by_family", + "top": 1, + "sort": [{"iv": 0.9}], + "evolve": true, + "upgrade": false + }, + { + "mode": "by_family", + "top": 1, + "sort": [{"ncp": "0.9"}], + "evolve": true, + "upgrade": false + }, + { + "mode": "by_family", + "top": 1, + "sort": ["cp"], + "evolve": false, + "upgrade": false + }, + { + "mode": "by_family", + "names": ["gym"], + "top": 3, + "sort": [{"iv": 0.9}, {"ncp": "0.9"}], + "evolve": true, + "upgrade": true + } + ] + } + } + ] +} +``` + +[[back to top](#pokemon-optimizer)] + +## Understand parameters +### enabled +| Parameter | Possible values | Default | +|-----------|-----------------|---------| +| `enabled` | `true`, `false` | `true` | + +Enable or disable the task. + +[[back to top](#pokemon-optimizer)] + +### transfer +| Parameter | Possible values | Default | +|------------|-----------------|---------| +| `transfer` | `true`, `false` | `false` | + +The `transfer` parameter activate or deactivate the transfer of Pokemon. + +At `true`, you allow the Pokemon Optimizer to transfer every Pokemon that are not good enough to be kept according to your own criteria. +
At `false`, and regardless of other parameters, no Pokemon is ever going to be transfered. + +Note that, whatever is the value you choose to give to that parameter, you will still see logs explaining which Pokemon are transfered. +
The purpose of this is to show you what choices are made by the Pokemon Optimizer. +It can help you rectify your configuration or guide you during manual transfer. + +`Exchanged Magikarp [IV 0.4] [CP 69] [481 candies]` + +[[back to top](#pokemon-optimizer)] + +### transfer_wait_min +| Parameter | Possible values | Default | +|---------------------|-----------------|---------| +| `transfer_wait_min` | `[0-N]` | `3` | + +This is the minimum time to wait after transferring a Pokemon. + +[[back to top](#pokemon-optimizer)] + +### transfer_wait_max +| Parameter | Possible values | Default | +|---------------------|-----------------|---------| +| `transfer_wait_max` | `[0-N]` | `5` | + +This is the maximum time to wait after transferring a Pokemon. + +[[back to top](#pokemon-optimizer)] + +### evolve +| Parameter | Possible values | Default | +|-----------|-----------------|---------| +| `evolve` | `true`, `false` | `false` | + +The `evolve` parameter activate or deactivate the evolution of Pokemon. + +At `true`, you allow the Pokemon Optimizer to evolve every Pokemon that are the best according to your own criteria. +
You also allow it to evolve lower quality Pokemon when [`evolve_for_xp`](#evolve_for_xp) parameter is `true`. +
At `false`, and regardless of other parameters, no Pokemon is ever going to be evolved. +
`evolve` parameter can be deactivated separately for each rule (see [`evolve`](#keep-evolve)). + +Note that, whatever is the value you choose to give to that parameter, you will still see logs explaining which Pokemon are evolved. +
The purpose of this is to show you what choices are made by the Pokemon Optimizer. +It can help you rectify your configuration or guide you during manual evolution. + +`Evolved Magikarp [IV 0.96] [CP 231] [+1000 xp] [82 candies]` + +[[back to top](#pokemon-optimizer)] + +### evolve_time +| Parameter | Possible values | Default | +|---------------|-----------------|---------| +| `evolve_time` | `[0-N]` | `25` | + +This is the duration of the evolution animation and time to wait after performing an evolution. +
The actual time used is randomized between more or less 10% of the parameter value. + +[[back to top](#pokemon-optimizer)] + +### evolve_for_xp +| Parameter | Possible values | Default | +|-----------------|-----------------|---------| +| `evolve_for_xp` | `true`, `false` | `true` | + +Let you choose if you want the Pokemon Otimizer to use your candies to evolve low quality Pokemon. + +Better quality Pokemon have priority for evolution and the Pokemon Optimizer will never evolve for xp if a better Pokemon is waiting for candies to evolve. +
These low quality Pokemon will only be used if you have plenty of candies left after evolving your best Pokemon. + +The below 2% rule help the Pokemon Optimizer to disregard rare Pokemon and focus on common Pokemon to evolve for xp. + +#### 2% rule +For each family of Pokemon, if, after evolving your best Pokemon, you have enough candies left to evolve 2% of your total bag capacity, the first rank of the family are eligible for xp evolution. +
If you do not have enough candies or Pokemon to evolve these 2%, they will be transfered. + +For example, for a bag total capacity of 250, you must have enough candies and Pokemon to evolve 5 of them for xp evolution. +
This usually means that the rarest Pokemon in your area will never be eligible since you never have 5 low quality of them at the point where your bag is full. + +[[back to top](#pokemon-optimizer)] + +### evolve_only_with_lucky_egg +| Parameter | Possible values | Default | +|------------------------------|-----------------|---------| +| `evolve_only_with_lucky_egg` | `true`, `false` | `false` | + +Force the Pokemon Optimizer to wait that a lucky egg is available to perform evolution. +
It is advised to keep this parameter to `false` since it is quite restrictive. +
You do not always have a lucky egg available and you still need to clean up your bag to make place for new Pokemon. + +At `true`, no evolution will be performed unless we have an available lucky egg to use before. + +[[back to top](#pokemon-optimizer)] + +### evolve_count_for_lucky_egg +| Parameter | Possible values | Default | +|------------------------------|-----------------|---------| +| `evolve_count_for_lucky_egg` | `[0-N]` | `80` | + +If you allow the Pokemon Optimizer to use a lucky egg, this parameter let you define the minimum number of Pokemons that must evolve when using a lucky egg. + +If a lucky egg is available, the Pokemon Optimizer is going to wait that number is reached to perform evolution. +
If you do not have any available lucky egg, the Pokemon Optimizer will ignore this parameter and evolution will be performed without lucky egg. +
It may take long time before reaching that number. + +[[back to top](#pokemon-optimizer)] + +### may_use_lucky_egg +| Parameter | Possible values | Default | +|---------------------|-----------------|---------| +| `may_use_lucky_egg` | `true`, `false` | `true` | + +Define whether you allow the Pokemon Optimizer to use a lucky egg before evolving Pokemon or not. +
At `true`, and if a lucky egg is available, the Pokemon Optimizer will wait for the [`evolve_count_for_lucky_egg`](#evolve_count_for_lucky_egg) Pokemon to use the lucky egg. +
At `false`, or when no luck egg is available, no lucky egg will be used. + +[[back to top](#pokemon-optimizer)] + +### upgrade +| Parameter | Possible values | Default | +|-----------|-----------------|---------| +| `upgrade` | `true`, `false` | `false` | + +The `upgrade` parameter activate or deactivate the upgrade (power-up) of Pokemon. + +At `true`, you allow the Pokemon Optimizer to upgrade every Pokemon that are the best according to your own criteria. +
At `false`, and regardless of other parameters, no Pokemon is ever going to be upgraded. +
`upgrade` parameter can be deactivated separately for each rule (see [`upgrade`](#keep-upgrade)). + +Note that, whatever is the value you choose to give to that parameter, you will still see logs explaining which Pokemon are upgraded. +
The purpose of this is to show you what choices are made by the Pokemon Optimizer. +It can help you rectify your configuration or guide you during manual power-up. + +`Upgraded Magikarp [IV 0.96] [CP 231] [81 candies] [132450 stardust]` + +[[back to top](#pokemon-optimizer)] + +### groups +| Parameter | Possible values | Default | +|-----------|-----------------|---------| +| `groups` | (see below) | `{}` | + +You can define `groups` of Pokemon to help you restrict rules to a specific set of Pokemon. +
You can then use these `groups` names in the [`names`](#keep-names) parameter of your rule to refer to list of Pokemon + +`groups` are list of Pokemon names: +``` +"groups": { + "gym": ["Dragonite", "Snorlax"], + "my_love": ["Pikachu"], + "vip": ["Lapras", "Arcanine", "Gyarados", "gym"], + "trash": ["!vip", "!my_love"] +}, +``` + +A same Pokemon name can appear in different `groups`. And `groups` may reference each others. +
Just like [`names`](#keep-names), you can also negate a group by preceding its name by a `!` or `-`. +
Including `groups` and negating others allow you to create group unions and/or intersections. + +[[back to top](#pokemon-optimizer)] + +### keep +| Parameter | Possible values | Default | +|-----------|-----------------|-------------| +| `keep` | (see below) | (see below) | + +This parameter is a list that contains as many element as you want. +
Each element of that list define a rule that select what Pokemon are the best. +
Each of these rules is going to be handled individually and will result in a list of Pokemon to keep. + +The conjunction of all rules define the list of all Pokemon to keep. +Every Pokemon not selected is candidate for transfer. + +``` +[ + {"mode": "by_family", "top": 1, "sort": [{"iv": 0.9}], "evolve": True, "upgrade": False}, + {"mode": "by_family", "top": 1, "sort": [{"ncp": 0.9}], "evolve": True, "upgrade": False}, + {"mode": "by_family", "top": 1, "sort": ["cp"], "evolve": False, "upgrade": False}, + {"mode": "by_family", "top": 3, "names": ["gym"], "sort": [{"iv": 0.9}, {"ncp": 0.9}], "evolve": True, "upgrade": True} +] +``` + +[[back to top](#pokemon-optimizer)] + +#### keep mode +| Parameter | Possible values | Default | +|-----------|--------------------------------------------|---------------| +| `mode` | `"by_pokemon"`, `"by_family"`, `"overall"` | `"by_family"` | + +The `mode` define how the Pokemon Optimizer is going to apply the rule. +- `"by_pokemon"` will apply the rule for each Pokemon. +
In that mode, each Pokemon will be compared to other Pokemon of the same name. +
This is the most conservative mode and the one that will result in the most number of Pokemon kept. + +- `"by_family"` will apply the rule for each Pokemon family. +
In that mode, each Pokemon will be compared to other Pokemon of the same family. + +- `"overall"` will apply the rule to the whole bag. +
In that mode, each Pokemon will be compared to all other Pokemon in the bag. +
This is the most aggressive mode and will result in the less number of Pokemon kept. + +[[back to top](#pokemon-optimizer)] + +#### keep names +| Parameter | Possible values | Default | +|-----------|-----------------|--------------------------| +| `names` | list of strings | `[]` = All Pokemon names | + +The `names` allow you to restrict a rule to a selected set of Pokemon. +
It is a list of Pokemon names or Pokemon [`groups`](#groups). +
You can negate a name by preceding it by a `!` or `-`. In that case, the rule apply to all except the negated names. +
You can combine Pokemon names and group names together in the same list. + +By default, rules apply to all Pokemons. +- In `by_family` mode, if a Pokemon name is present in the `names` list, it refer to all Pokemon in that Pokemon family. +- In `overall` mode, the `names` list behave as a filter on the whole Pokemon bag. + +[[back to top](#pokemon-optimizer)] + +#### keep top +| Parameter | Possible values | Default | +|-----------|-----------------------|---------| +| `top` | `0`, `]0-1[`, `[1-N]` | `0` | + +This value define how many Pokemon, at the top of your selection, you wish to keep. + +- If the value `N` is an integer greater or equal to `1`, it means you wish to keep the N best Pokemon of your selection. +
Pokemon will be sorted according to your sorting rule and the `Nth` first will be kept. +
In case of equality between Pokemon, it may result in more than `N` Pokemon selected, but never less. +
See examples here + +- If the value `N` is a decimal between `0` and `1`, it is a ratio relative to the best Pokemon. +
Pokemon will be sorted according to your sorting rule and the best will be elected. +
All Pokemon whose criteria does not deviate more than `N*100 %` of the best will also be selected. +
See examples here + +- If the value `N` is `0` or negative, all Pokemon in the selected will be kept. +
See examples here + +[[back to top](#pokemon-optimizer)] + +#### keep sort +| Parameter | Possible values | Default | +|-----------|-----------------|---------| +| `sort` | (see below) | `[]` | + +Define according to which criteria you want to sort your Pokemon. +
Available criteria are: + +| Criteria | Description | +|----------------------|------------------------------------------------------------------------------| +| `iv` | individual value between 0 and 1 | +| `ivcp` | iv weighted so that for equal iv, attack > defense > stamina | +| `cp` | combat power (can be increased with candies) | +| `cp_exact` | combat power (not rounded) | +| `ncp` | normalized cp = ratio cp / max_cp | +| `iv_attack` | attack component of iv between 0 and 15 | +| `iv_defense` | defense component of iv between 0 and 15 | +| `iv_stamina` | stamina component of iv between 0 and 15 | +| `dps` | raw dps based on the moves of the pokemon | +| `dps_attack` | estimated average dps when attacking | +| `attack_perfection` | ratio `dps_attack` / `best_dps_attack`. Return same order as `dps_attack` | +| `dps_defense` | estimated average dps when defending | +| `defense_perfection` | ratio `dps_defense` / `best_dps_defense`. Return same order as `dps_defense` | +| `hp` | current health points | +| `hp_max` | max health points | + +You can put multiple criteria in the list by separating them by a comma: `"sort": ["iv", "cp"]` +
If multiple criteria are present, Pokemon will be ranked according to the first, then if equals the second, etc... + +In addition to define sorting criteria, the `sort` list may contain minimum requirements for evolution and upgrade: +- `"top": 1, "sort": ["iv"], "evolve": True` will rank Pokemon by `iv`. +
It will keep and evolve the best of them. +- `"top": 1, "sort": [{"iv": 0.9}], "evolve": True` will rank Pokemon by `iv`. +
It will keep the best of them and evolve it only if its `iv` is greater than `0.9`. +- `"top": 3, "sort": [{"iv": 0.9}, {"cp": 1200}], "evolve": True` will rank Pokemon by `iv` and then `cp`. +
It will keep the top 3 of them and evolve them only if their `iv` is greater than `0.9` and their `cp` greater than `1200`. + +[[back to top](#pokemon-optimizer)] + +#### keep evolve +| Parameter | Possible values | Default | +|-----------|-----------------|---------| +| `evolve` | `true`, `false` | `true` | + +The parameter allow you to selectively control what Pokemon to evolve. + +At `true`, all Pokemon selected and meeting the minimum evolution criteria (see [`sort`](#keep-sort)) will be evolved. +
At `false`, Pokemon selected will not be eligible for evolution, unless they are also selected by another rule that allow them to evolve. + +[[back to top](#pokemon-optimizer)] + +#### keep upgrade +| Parameter | Possible values | Default | +|-----------|-----------------|---------| +| `upgrade` | `true`, `false` | `false` | + +The parameter allow you to selectively control what Pokemon to upgrade. + +At `true`, all Pokemon selected and meeting the minimum upgrade criteria (see [`sort`](#keep-sort)) will be upgraded. +
At `false`, Pokemon selected will not be eligible for upgrade, unless they are also selected by another rule that allow them to upgrade. + +[[back to top](#pokemon-optimizer)] + +## Examples of configuration +> Keep the 2 best `iv` of every single Pokemon, and evolve them if they are over 0.9 `iv`. + +``` +{ + "mode": "by_pokemon", + "top": 2, + "sort": [{"iv": 0.9}], + "evolve": true +}, +``` + +> Keep my 10 best `cp` Dragonite and Snorlax to fight gyms. + +``` +{ + "mode": "by_pokemon", + "names": ["Dragonite", "Snorlax"] + "top": 10, + "sort": ["cp"], + "evolve": false +}, +``` + +> Keep my Gyarados with the best moveset for attack. + +``` +{ + "mode": "by_pokemon", + "names": ["Gyarados"] + "top": 1, + "sort": ["dps_attack"], + "evolve": false +}, +``` + +> I don't want you to use my candies for my loved Pokemon, But for others, it is ok. + +``` +"groups": { + "my loves ones" : ["Dragonite", "Snorlax", "Caterpie"] +}, +"keep": [ + { + "mode": "by_family", + "names": ["my loves ones"] + "top": 1, + "sort": ["iv"], + "evolve": false + }, + { + "mode": "by_family", + "names": ["!my loves ones"] + "top": 1, + "sort": ["iv"], + "evolve": true + } + // + Exclude `my loves ones` from any other rule +] +``` + +> Do not touch my Dragonites ! + +``` +{ + "mode": "by_pokemon", + "names": ["Dragonite"] +}, +// + Exclude Dragonite from any other rule +``` + +[[back to top](#pokemon-optimizer)] + +# Eevee case + +For Eevee Pokemon family, and any other family with multiple paths of evolution, the Pokemon Optimizer behaves as if the chances of getting a specific evolution were random and equal. +
In practice, here are the effects you might notice regarding Eevee family: +- If you are missing one version of evolution, every Eevee is a possible candidate to the become the best Eevee you have for that specific evolution. +
So as long as an evolution version is missing, the Pokemon Optimizer will tentatively try to keep and evolve all Eevees. + +- Once you have all version of evolution, things are not yet simple. Every every better than the worst evolution you have is a candidate to replace it. +
The Pokemon Optimizer will tentatively try to keep and evolve all Eevees that may replace the worst evolution you have. + +- If you deactivate the global `evolve` parameter, the Pokemon Optimizer will not apply above rules since it considers you are manually controlling the evolution of your Eevees. + +[[back to top](#pokemon-optimizer)] diff --git a/map-chat/javascript/main.js b/map-chat/javascript/main.js index d09e798edc..c1f5a009b2 100644 --- a/map-chat/javascript/main.js +++ b/map-chat/javascript/main.js @@ -26,15 +26,34 @@ function initialiseEventBus(){ var pLoadR2 = pLoadR.split(","); var olat = pLoadR2[0] var olong = pLoadR2[1] - var sessid = pLoadR2[2] - var ico = pLoadR2[3] - var expir = pLoadR2[4] - var pokenick = pLoadR2[5] - var path = "./images/p/" - var icon = path + "0" + ico + ".png" - var icostr = icon.toString(); - displayMessageOnMap(payload, olat, olong, sessid, icostr, expir, pokenick); - console.debug('[CATCHABLE]', pokenick, '(' + olat + ',' + olong + ')'); + + var pokemon_id = parseInt(pLoadR2[2]) + if (pokemon_id>0 && pokemon_id< 160){ + var ico = pLoadR2[2] + var expir = pLoadR2[3] + var pokenick = pLoadR2[4] + var sessid = pLoadR2[2] + var path = "./images/p/" + console.log('icon is '+ico) + var icon = path + "0" + ico.replace(" ","") + ".png" + var icostr = icon.toString(); + displayMessageOnMap(payload, olat, olong, sessid, icostr, expir, pokenick); + console.debug('[CATCHABLE]', pokenick, '(' + olat + ',' + olong + ')'); + } else { + var pokemon_id = parseInt(pLoadR2[3]) + if (pokemon_id>0 && pokemon_id< 160){ + var ico = pLoadR2[3] + var expir = pLoadR2[4] + var pokenick = pLoadR2[5] + var sessid = pLoadR2[5] + var path = "./images/p/" + console.log('icon is '+ico) + var icon = path + "0" + ico.replace(" ","") + ".png" + var icostr = icon.toString(); + displayMessageOnMap(payload, olat, olong, sessid, icostr, expir, pokenick); + console.debug('[CATCHABLE]', pokenick, '(' + olat + ',' + olong + ')'); + } + } } else { console.debug(topic); } diff --git a/pokecli.py b/pokecli.py index 6598efca34..e81cf239f6 100644 --- a/pokecli.py +++ b/pokecli.py @@ -38,12 +38,10 @@ import signal import string import subprocess -from datetime import timedelta from getpass import getpass from pgoapi.exceptions import NotLoggedInException, ServerSideRequestThrottlingException, ServerBusyOrOfflineException, NoPlayerPositionSetException from geopy.exc import GeocoderQuotaExceeded -from pokemongo_bot import inventory from pokemongo_bot import PokemonGoBot, TreeConfigBuilder from pokemongo_bot.base_dir import _base_dir from pokemongo_bot.health_record import BotEvent @@ -65,7 +63,10 @@ logger = logging.getLogger('cli') logger.setLevel(logging.INFO) -class SIGINTRecieved(Exception): pass + +class SIGINTRecieved(Exception): + pass + def main(): bot = False @@ -81,21 +82,29 @@ def initialize_task(bot, config): def initialize(config): bot = PokemonGoBot(config) return bot - - def start_bot(bot,config): + + def start_bot(bot, config): bot.start() - initialize_task(bot,config) + initialize_task(bot, config) bot.metrics.capture_stats() bot.health_record = BotEvent(config) return bot def get_commit_hash(): try: - hash = subprocess.check_output(['git', 'rev-parse', 'HEAD'], stderr=subprocess.STDOUT)[:-1] - - return hash if all(c in string.hexdigits for c in hash) else "not found" + hash = subprocess.check_output(['git', 'rev-parse', 'HEAD'], + stderr=subprocess.STDOUT) + if all(c in string.hexdigits for c in hash[:-1]): + with open('version', 'w') as f: + f.write(hash) except: - return "not found" + pass + + if not os.path.exists('version'): + return 'unknown' + + with open('version') as f: + return f.read()[:8] try: logger.info('PokemonGO Bot v1.0') @@ -116,7 +125,7 @@ def get_commit_hash(): while not finished: try: bot = initialize(config) - bot = start_bot(bot,config) + bot = start_bot(bot, config) config_changed = check_mod(config_file) bot.event_manager.emit( @@ -132,10 +141,11 @@ def get_commit_hash(): logger.info('Config changed! Applying new config.') config, _ = init_config() - if config.live_config_update_tasks_only: initialize_task(bot, config) - else: + if config.live_config_update_tasks_only: + initialize_task(bot, config) + else: bot = initialize(config) - bot = start_bot(bot,config) + bot = start_bot(bot, config) except KeyboardInterrupt: bot.event_manager.emit( @@ -232,6 +242,7 @@ def get_commit_hash(): data={'path': cached_forts_path} ) + def check_mod(config_file): check_mod.mtime = os.path.getmtime(config_file) @@ -245,6 +256,7 @@ def compare_mtime(): return compare_mtime + def report_summary(bot): if bot.metrics.start_time is None: return # Bot didn't actually start, no metrics to show. @@ -270,6 +282,7 @@ def report_summary(bot): if metrics.most_perfect is not None: logger.info('Most Perfect Pokemon: {}'.format(metrics.most_perfect['desc'])) + def init_config(): parser = argparse.ArgumentParser() config_file = os.path.join(_base_dir, 'configs', 'config.json') @@ -390,8 +403,7 @@ def _json_loader(filename): load, short_flag="-wmax", long_flag="--walk_max", - help= - "Walk instead of teleport with given speed", + help="Walk instead of teleport with given speed", type=float, default=2.5 ) @@ -400,8 +412,7 @@ def _json_loader(filename): load, short_flag="-wmin", long_flag="--walk_min", - help= - "Walk instead of teleport with given speed", + help="Walk instead of teleport with given speed", type=float, default=2.5 ) @@ -632,7 +643,7 @@ def _json_loader(filename): type=bool, default=False ) - + # Start to parse other attrs config = parser.parse_args() if not config.username and 'username' not in load: @@ -697,10 +708,10 @@ def task_configuration_error(flag_name): if "daily_catch_limit" in load: logger.warning('The daily_catch_limit argument has been moved into the CatchPokemon Task') - + if "logging_color" in load: logger.warning('The logging_color argument has been moved into the logging config section') - + if config.walk_min < 1: parser.error("--walk_min is out of range! (should be >= 1.0)") return None @@ -727,6 +738,7 @@ def task_configuration_error(flag_name): fix_nested_config(config) return config, config_file + def add_config(parser, json_config, short_flag=None, long_flag=None, **kwargs): if not long_flag: raise Exception('add_config calls requires long_flag parameter!') @@ -734,7 +746,7 @@ def add_config(parser, json_config, short_flag=None, long_flag=None, **kwargs): full_attribute_path = long_flag.split('--')[1] attribute_name = full_attribute_path.split('.')[-1] - if '.' in full_attribute_path: # embedded config! + if '.' in full_attribute_path: # embedded config! embedded_in = full_attribute_path.split('.')[0: -1] for level in embedded_in: json_config = json_config.get(level, {}) @@ -757,6 +769,7 @@ def fix_nested_config(config): config_dict[new_key] = value del config_dict[key] + def parse_unicode_str(string): try: return string.decode('utf8') diff --git a/pokemongo_bot/__init__.py b/pokemongo_bot/__init__.py index 03d76dc7ce..38e440ba59 100644 --- a/pokemongo_bot/__init__.py +++ b/pokemongo_bot/__init__.py @@ -36,14 +36,16 @@ from pokemongo_bot.datastore import _init_database, Datastore from worker_result import WorkerResult from tree_config_builder import ConfigException, MismatchTaskApiVersion, TreeConfigBuilder -from inventory import init_inventory +from inventory import init_inventory, player from sys import platform as _platform from pgoapi.protos.POGOProtos.Enums import BadgeType_pb2 import struct + class FileIOException(Exception): pass + class PokemonGoBot(Datastore): @property def position(self): @@ -121,7 +123,9 @@ def start(self): self._setup_event_system() self._setup_logging() self.sleep_schedule = SleepSchedule(self, self.config.sleep_schedule) if self.config.sleep_schedule else None - if self.sleep_schedule: self.sleep_schedule.work() + if self.sleep_schedule: + self.sleep_schedule.work() + self._setup_api() self._load_recent_forts() init_inventory(self) @@ -186,7 +190,7 @@ def _register_events(self): self.event_manager.register_event('set_start_location') self.event_manager.register_event('load_cached_location') self.event_manager.register_event('location_cache_ignored') - + self.event_manager.register_event('debug') # ignore candy above threshold @@ -198,11 +202,6 @@ def _register_events(self): 'threshold' ) ) - - - - - self.event_manager.register_event( 'position_update', parameters=( @@ -227,7 +226,6 @@ def _register_events(self): ) ) - self.event_manager.register_event('location_cache_error') self.event_manager.register_event('bot_start') @@ -237,7 +235,10 @@ def _register_events(self): # sleep stuff self.event_manager.register_event( 'next_sleep', - parameters=('time',) + parameters=( + 'time', + 'duration' + ) ) self.event_manager.register_event( 'bot_sleep', @@ -436,6 +437,10 @@ def _register_events(self): 'pokemon_evolved', parameters=('pokemon', 'iv', 'cp', 'xp', 'candy') ) + self.event_manager.register_event( + 'pokemon_upgraded', + parameters=('pokemon', 'iv', 'cp', 'candy', 'stardust') + ) self.event_manager.register_event('skip_evolve') self.event_manager.register_event('threw_berry_failed', parameters=('status_code',)) self.event_manager.register_event('vip_pokemon') @@ -601,9 +606,6 @@ def _register_events(self): 'move_to_map_pokemon', parameters=('message') ) - - - # cached recent_forts self.event_manager.register_event('loaded_cached_forts') self.event_manager.register_event('cached_fort') @@ -636,7 +638,8 @@ def tick(self): self.health_record.heartbeat() self.cell = self.get_meta_cell() - if self.sleep_schedule: self.sleep_schedule.work() + if self.sleep_schedule: + self.sleep_schedule.work() now = time.time() * 1000 @@ -774,7 +777,7 @@ def _setup_logging(self): if self.config.logging: logging_format = '%(message)s' logging_format_options = '' - + if ('show_log_level' not in self.config.logging) or self.config.logging['show_log_level']: logging_format = '[%(levelname)s] ' + logging_format if ('show_process_name' not in self.config.logging) or self.config.logging['show_process_name']: @@ -782,7 +785,7 @@ def _setup_logging(self): if ('show_datetime' not in self.config.logging) or self.config.logging['show_datetime']: logging_format = '[%(asctime)s] ' + logging_format logging_format_options = '%Y-%m-%d %H:%M:%S' - + formatter = Formatter(logging_format,logging_format_options) for handler in logging.root.handlers[:]: handler.setFormatter(formatter) @@ -828,7 +831,7 @@ def login(self): formatted="Login procedure started." ) lat, lng = self.position[0:2] - self.api.set_position(lat, lng, self.alt) # or should the alt kept to zero? + self.api.set_position(lat, lng, self.alt) # or should the alt kept to zero? while not self.api.login( self.config.auth_service, @@ -1098,7 +1101,7 @@ def _set_starting_position(self): level='debug', formatted='Loading cached location...' ) - + json_file = os.path.join(_base_dir, 'data', 'last-location-%s.json' % self.config.username) try: @@ -1106,7 +1109,7 @@ def _set_starting_position(self): location_json = json.load(infile) except (IOError, ValueError): # Unable to read json file. - # File may be corrupt. Create a new one. + # File may be corrupt. Create a new one. location_json = [] except: raise FileIOException("Unexpected error reading from {}".web_inventory) @@ -1220,7 +1223,7 @@ def heartbeat(self): responses = request.call() if responses['responses']['GET_PLAYER']['success'] == True: - #we get the player_data anyway, might as well store it + # we get the player_data anyway, might as well store it self._player = responses['responses']['GET_PLAYER']['player_data'] self.event_manager.emit( 'player_data', @@ -1230,7 +1233,7 @@ def heartbeat(self): data={'player_data': self._player} ) if responses['responses']['CHECK_AWARDED_BADGES']['success'] == True: - #store awarded_badges reponse to be used in a task or part of heartbeat + # store awarded_badges reponse to be used in a task or part of heartbeat self._awarded_badges = responses['responses']['CHECK_AWARDED_BADGES'] if self._awarded_badges.has_key('awarded_badges'): @@ -1245,9 +1248,11 @@ def heartbeat(self): level='info', formatted='awarded badge: {badge}, lvl {level}', data={'badge': badgename, - 'level' : badgelevel } + 'level': badgelevel} ) - human_behaviour.action_delay(3,10) + human_behaviour.action_delay(3, 10) + + inventory.refresh_inventory() try: self.web_update_queue.put_nowait(True) # do this outside of thread every tick @@ -1260,34 +1265,21 @@ def update_web_location_worker(self): self.update_web_location() def display_player_info(self): - inventory_items = self.api.get_inventory() - inventory_items = inventory_items['responses']['GET_INVENTORY']['inventory_delta']['inventory_items'] - player_stats = next((x["inventory_item_data"]["player_stats"] - for x in inventory_items - if x.get("inventory_item_data", {}).get("player_stats", {})), - None) + player_stats = player() if player_stats: + nextlvlxp = (int(player_stats.next_level_xp) - int(player_stats.exp)) + self.logger.info( + 'Level: {}'.format(player_stats.level) + + ' (Next Level: {} XP)'.format(nextlvlxp) + + ' (Total: {} XP)' + ''.format(player_stats.exp)) - nextlvlxp = (int(player_stats.get('next_level_xp', 0)) - int(player_stats.get('experience', 0))) - - if 'level' in player_stats and 'experience' in player_stats: - self.logger.info( - 'Level: {level}'.format( - **player_stats) + - ' (Next Level: {} XP)'.format( - nextlvlxp) + - ' (Total: {experience} XP)' - ''.format(**player_stats)) - - if 'pokemons_captured' in player_stats and 'poke_stop_visits' in player_stats: - self.logger.info( - 'Pokemon Captured: ' - '{pokemons_captured}'.format( - **player_stats) + - ' | Pokestops Visited: ' - '{poke_stop_visits}'.format( - **player_stats)) + self.logger.info( + 'Pokemon Captured: ' + '{}'.format(player_stats.pokemons_captured) + + ' | Pokestops Visited: ' + '{}'.format(player_stats.poke_stop_visits)) def get_forts(self, order_by_distance=False): forts = [fort @@ -1322,12 +1314,18 @@ def _load_recent_forts(self): if not self.config.forts_cache_recent_forts: return - cached_forts_path = os.path.join(_base_dir, 'data', 'recent-forts-%s.json' % self.config.username) try: # load the cached recent forts - with open(cached_forts_path) as f: - cached_recent_forts = json.load(f) + cached_recent_forts = [] + try: + with open(cached_forts_path) as f: + cached_recent_forts = json.load(f) + except (IOError, ValueError) as e: + self.logger.info('[x] Error while opening cached forts: %s' % e, 'red') + pass + except: + raise FileIOException("Unexpected error opening {}".cached_forts_path) num_cached_recent_forts = len(cached_recent_forts) num_recent_forts = len(self.recent_forts) diff --git a/pokemongo_bot/base_task.py b/pokemongo_bot/base_task.py index da671776bd..151b9006a5 100644 --- a/pokemongo_bot/base_task.py +++ b/pokemongo_bot/base_task.py @@ -33,7 +33,7 @@ def emit_event(self, event, sender=None, level='info', formatted='', data={}): # Print log only if X seconds are passed from last log try: - if (time.time() - self.last_log_time) > self.config.get('log_interval', 0): + if (time.time() - self.last_log_time) >= self.config.get('log_interval', 0): self.last_log_time = time.time() self.bot.event_manager.emit( event, diff --git a/pokemongo_bot/cell_workers/collect_level_up_reward.py b/pokemongo_bot/cell_workers/collect_level_up_reward.py index 4e80d1fb62..ff0c8c5af2 100644 --- a/pokemongo_bot/cell_workers/collect_level_up_reward.py +++ b/pokemongo_bot/cell_workers/collect_level_up_reward.py @@ -12,12 +12,12 @@ class CollectLevelUpReward(BaseTask): def initialize(self): self._process_config() - self.current_level = self._get_current_level() + self.current_level = inventory.player().level self.previous_level = 0 def work(self): if self._should_run(): - self.current_level = self._get_current_level() + self.current_level = inventory.player().level if self.collect_reward: # let's check level reward on bot initialization @@ -70,24 +70,3 @@ def _collect_level_reward(self): 'items': data } ) - - def _get_current_level(self): - level = 0 - response_dict = self.bot.api.get_inventory() - data = (response_dict - .get('responses', {}) - .get('GET_INVENTORY', {}) - .get('inventory_delta', {}) - .get('inventory_items', {})) - - for item in data: - level = (item - .get('inventory_item_data', {}) - .get('player_stats', {}) - .get('level', 0)) - - # we found a level, no need to continue iterate - if level: - break - - return level diff --git a/pokemongo_bot/cell_workers/evolve_pokemon.py b/pokemongo_bot/cell_workers/evolve_pokemon.py index 431633aab8..7822f4379e 100644 --- a/pokemongo_bot/cell_workers/evolve_pokemon.py +++ b/pokemongo_bot/cell_workers/evolve_pokemon.py @@ -15,7 +15,8 @@ def __init__(self, bot, config): def initialize(self): self.api = self.bot.api - self.evolve_all = self.config.get('evolve_all', []) + self.evolve_list = self.config.get('evolve_list', []) + self.donot_evolve_list = self.config.get('donot_evolve_list', []) self.min_evolve_speed = self.config.get('min_evolve_speed', 25) self.max_evolve_speed = self.config.get('max_evolve_speed', 30) self.first_evolve_by = self.config.get('first_evolve_by', 'cp') @@ -26,36 +27,37 @@ def initialize(self): self._validate_config() def _validate_config(self): - if isinstance(self.evolve_all, basestring): - self.evolve_all = [str(pokemon_name).strip() for pokemon_name in self.evolve_all.split(',')] + if isinstance(self.evolve_list, basestring): + self.evolve_list = [str(pokemon_name).strip() for pokemon_name in self.evolve_list.split(',')] + + if isinstance(self.donot_evolve_list, basestring): + self.donot_evolve_list = [str(pokemon_name).strip() for pokemon_name in self.donot_evolve_list.split(',')] if 'evolve_speed' in self.config: - self.logger.warning("evolve_speed is deprecated, please use instead 'min_evolve_speed' and 'max_evolved_speed'.") + self.logger.warning("evolve_speed is deprecated, instead please use 'min_evolve_speed' and 'max_evolved_speed'.") + + if 'evolve_all' in self.config: + self.logger.warning("evolve_all is deprecated, instead please use 'evolve_list' and 'donot_evolve_list'.") def work(self): if not self._should_run(): return - evolve_list = self._sort_and_filter() + filtered_list = self._sort_and_filter() - if self.evolve_all[0] != 'all': - # check for negation - negate = filter(lambda x: x[0] == '-', self.evolve_all) + if (len(self.evolve_list) > 0) and self.evolve_list[0] != 'all': + filtered_list = filter(lambda x: x.name in self.evolve_list, filtered_list) - # if there are things to negate - if len(negate) > 0: - evolve_list = filter(lambda x: '-' + x.name not in negate, evolve_list) - else: - # filter out non-listed pokemons - evolve_list = filter(lambda x: x.name in self.evolve_all, evolve_list) + if (len(self.donot_evolve_list) > 0) and self.donot_evolve_list[0] != 'none': + filtered_list = filter(lambda pokemon: pokemon.name not in self.donot_evolve_list, filtered_list) cache = {} - for pokemon in evolve_list: + for pokemon in filtered_list: if pokemon.can_evolve_now(): self._execute_pokemon_evolve(pokemon, cache) def _should_run(self): - if not self.evolve_all or self.evolve_all[0] == 'none': + if not self.evolve_list or self.evolve_list[0] == 'none': return False # Evolve all is used - Use Lucky egg only at the first tick diff --git a/pokemongo_bot/cell_workers/follow_path.py b/pokemongo_bot/cell_workers/follow_path.py index 6b2ed51cbe..34cdb46108 100644 --- a/pokemongo_bot/cell_workers/follow_path.py +++ b/pokemongo_bot/cell_workers/follow_path.py @@ -11,7 +11,7 @@ from pokemongo_bot.worker_result import WorkerResult from pgoapi.utilities import f2i from random import uniform -from utils import getSeconds +from utils import getSeconds, format_dist from datetime import datetime as dt, timedelta STATUS_MOVING = 0 @@ -26,6 +26,8 @@ def initialize(self): self.points = self.load_path() self.status = STATUS_MOVING self.loiter_end_time = 0 + self.distance_unit = self.bot.config.distance_unit + self.append_unit = False if self.path_start_mode == 'closest': self.ptr = self.find_closest_point_idx(self.points) @@ -181,14 +183,14 @@ def work(self): data={ 'last_position': (last_lat, last_lng, last_alt), 'current_position': (lat, lng, alt), - 'distance': dist, - 'distance_unit': 'm' + 'distance': format_dist(dist,self.distance_unit,self.append_unit), + 'distance_unit': self.distance_unit } ) if dist <= 1 or (self.bot.config.walk_min > 0 and is_at_destination) or (self.status == STATUS_LOITERING and time.time() >= self.loiter_end_time): - if "loiter" in point and self.status != STATUS_LOITERING: - print("Loitering {} seconds".format(point["loiter"])) + if "loiter" in point and self.status != STATUS_LOITERING: + self.logger.info("Loitering for {} seconds...".format(point["loiter"])) self.status = STATUS_LOITERING self.loiter_end_time = time.time() + point["loiter"] return WorkerResult.SUCCESS diff --git a/pokemongo_bot/cell_workers/incubate_eggs.py b/pokemongo_bot/cell_workers/incubate_eggs.py index 2bf147d5ab..5cc8e7f7e3 100644 --- a/pokemongo_bot/cell_workers/incubate_eggs.py +++ b/pokemongo_bot/cell_workers/incubate_eggs.py @@ -125,7 +125,6 @@ def _apply_incubators(self, available_eggs, available_incubators): def _check_inventory(self, lookup_ids=[]): inv = {} - response_dict = self.bot.api.get_inventory() matched_pokemon = [] temp_eggs = [] temp_used_incubators = [] @@ -134,7 +133,7 @@ def _check_inventory(self, lookup_ids=[]): inv = reduce( dict.__getitem__, ["responses", "GET_INVENTORY", "inventory_delta", "inventory_items"], - response_dict + inventory.jsonify_inventory() ) for inv_data in inv: inv_data = inv_data.get("inventory_item_data", {}) diff --git a/pokemongo_bot/cell_workers/move_to_map_pokemon.py b/pokemongo_bot/cell_workers/move_to_map_pokemon.py index 1003f781e0..65e536dc6b 100644 --- a/pokemongo_bot/cell_workers/move_to_map_pokemon.py +++ b/pokemongo_bot/cell_workers/move_to_map_pokemon.py @@ -70,27 +70,10 @@ from random import uniform from pokemongo_bot.constants import Constants -# Update the map if more than N meters away from the center. (AND'd with -# UPDATE_MAP_MIN_TIME_MINUTES) ULTRABALL_ID = 3 GREATBALL_ID = 2 POKEBALL_ID = 1 -UPDATE_MAP_MIN_DISTANCE_METERS = 500 -# Update the map if it hasn't been updated in n seconds. (AND'd with -# UPDATE_MAP_MIN_DISTANCE_METERS) -UPDATE_MAP_MIN_TIME_SEC = 120 - -# Number of seconds to sleep between teleporting to a snipped Pokemon. -SNIPE_SLEEP_SEC = 2 - -# Max Sniping in chain -SNIPE_MAX_IN_CHAIN = 2 - -# Don't call sniper every time in workers -SNIPE_SKIP_IN_ROUND = 5 - -DEBUG_ON = False class MoveToMapPokemon(BaseTask): @@ -135,11 +118,11 @@ def get_pokemon_from_social(self): pokemon['is_vip'] = pokemon['name'] in self.bot.config.vips if pokemon['name'] not in self.config['catch']: - if DEBUG_ON: + if self.config.get('debug', False): self._emit_failure("Not catching {}".format(pokemon['name'])) continue else: - if DEBUG_ON: + if self.config.get('debug', False): self._emit_log("Catching {}".format(pokemon['name'])) @@ -262,8 +245,8 @@ def update_map_location(self): # update map when 500m away from center and last update longer than 2 minutes away now = int(time.time()) - if (dist > UPDATE_MAP_MIN_DISTANCE_METERS and - now - self.last_map_update > UPDATE_MAP_MIN_TIME_SEC): + if (dist > self.config.get('update_map_min_distance_meters', 500) and + now - self.last_map_update > self.config.get('update_map_min_time_sec', 120)): requests.post( '{}/next_loc?lat={}&lon={}'.format(self.config['address'], self.bot.position[0], @@ -289,10 +272,10 @@ def snipe(self, pokemon): self._teleport_to(pokemon) catch_worker = PokemonCatchWorker(pokemon, self.bot, self.config) api_encounter_response = catch_worker.create_encounter_api_call() - time.sleep(SNIPE_SLEEP_SEC) + time.sleep(self.config.get('snipe_sleep_sec', 2)) self._teleport_back(last_position) self.bot.api.set_position(last_position[0], last_position[1], self.alt, False) - time.sleep(SNIPE_SLEEP_SEC) + time.sleep(self.config.get('snipe_sleep_sec', 2)) self.bot.heartbeat() catch_worker.work(api_encounter_response) self.add_caught(pokemon) @@ -310,7 +293,7 @@ def work(self): ultraballs_quantity = inventory.items().get(ULTRABALL_ID).count if (pokeballs_quantity + superballs_quantity + ultraballs_quantity) < self.min_ball: - if DEBUG_ON: + if self.config.get('debug', False): self._emit_log("Not enough balls to start sniping (have {}, {} needed)".format(pokeballs_quantity + superballs_quantity + ultraballs_quantity, self.min_ball)) return WorkerResult.SUCCESS @@ -318,8 +301,8 @@ def work(self): if self.bot.config.enable_social: if self.config['snipe']: self.by_pass_times = self.by_pass_times + 1 - if self.by_pass_times < SNIPE_SKIP_IN_ROUND: - if DEBUG_ON: + if self.by_pass_times < self.config.get('skip_rounds', 30): + if self.config.get('debug', False): self._emit_log("Skipping pass {}".format(self.by_pass_times)) return WorkerResult.SUCCESS self.by_pass_times = 0 @@ -335,12 +318,12 @@ def work(self): pokemon_list.sort(key=lambda x: x['is_vip'], reverse=True) if len(pokemon_list) < 1: - if DEBUG_ON: + if self.config.get('debug', False): self._emit_log("No pokemons in list to snipe") return WorkerResult.SUCCESS pokemon = pokemon_list[0] - if DEBUG_ON: + if self.config.get('debug', False): self._emit_log('How many pokemon in list: {}'.format(len(pokemon_list))) if self.config['snipe']: if self.snipe_high_prio_only: @@ -349,12 +332,12 @@ def work(self): if self.snipe_high_prio_threshold < pokemon['priority']: self.snipe(pokemon) count = count +1 - if count >= SNIPE_MAX_IN_CHAIN: + if count >= self.config.get('snipe_max_in_chain', 2): return WorkerResult.SUCCESS if count is not 1: - time.sleep(SNIPE_SLEEP_SEC*5) + time.sleep(self.config.get('snipe_sleep_sec', 2)*5) else: - if DEBUG_ON: + if self.config.get('debug', False): self._emit_log('this pokemon is not good enough to snipe {}'.format(pokemon)) return WorkerResult.SUCCESS else: diff --git a/pokemongo_bot/cell_workers/pokemon_optimizer.py b/pokemongo_bot/cell_workers/pokemon_optimizer.py index 41b1c06f96..d2062ba80d 100644 --- a/pokemongo_bot/cell_workers/pokemon_optimizer.py +++ b/pokemongo_bot/cell_workers/pokemon_optimizer.py @@ -1,4 +1,5 @@ -import copy +import difflib +import itertools import json import math import os @@ -25,23 +26,32 @@ def __init__(self, bot, config): super(PokemonOptimizer, self).__init__(bot, config) def initialize(self): - self.family_by_family_id = {} self.max_pokemon_storage = inventory.get_pokemon_inventory_size() self.last_pokemon_count = 0 + self.pokemon_names = [p.name for p in inventory.pokemons().STATIC_DATA] + self.stardust_count = 0 + self.ongoing_stardust_count = 0 + + pokemon_upgrade_cost_file = os.path.join(_base_dir, "data", "pokemon_upgrade_cost.json") + + with open(pokemon_upgrade_cost_file, "r") as fd: + self.pokemon_upgrade_cost = json.load(fd) self.config_transfer = self.config.get("transfer", False) + self.config_transfer_wait_min = self.config.get("transfer_wait_min", 3) + self.config_transfer_wait_max = self.config.get("transfer_wait_max", 5) self.config_evolve = self.config.get("evolve", False) - self.config_evolve_time = self.config.get("evolve_time", 20) + self.config_evolve_time = self.config.get("evolve_time", 25) self.config_evolve_for_xp = self.config.get("evolve_for_xp", True) self.config_evolve_only_with_lucky_egg = self.config.get("evolve_only_with_lucky_egg", False) - self.config_evolve_count_for_lucky_egg = self.config.get("evolve_count_for_lucky_egg", 92) + self.config_evolve_count_for_lucky_egg = self.config.get("evolve_count_for_lucky_egg", 80) self.config_may_use_lucky_egg = self.config.get("may_use_lucky_egg", False) - self.config_keep = self.config.get("keep", [{"top": 1, "evolve": True, "sort": ["iv"]}, - {"top": 1, "evolve": True, "sort": ["ncp"]}, - {"top": 1, "evolve": False, "sort": ["cp"]}]) - - self.config_transfer_wait_min = self.config.get("transfer_wait_min", 1) - self.config_transfer_wait_max = self.config.get("transfer_wait_max", 4) + self.config_upgrade = self.config.get("upgrade", False) + self.config_groups = self.config.get("groups", {"gym": ["Dragonite", "Snorlax", "Lapras", "Arcanine"]}) + self.config_keep = self.config.get("keep", [{"mode": "by_family", "top": 1, "sort": [{"iv": 0.9}], "evolve": True, "upgrade": False}, + {"mode": "by_family", "top": 1, "sort": [{"ncp": 0.9}], "evolve": True, "upgrade": False}, + {"mode": "by_family", "top": 1, "sort": ["cp"], "evolve": False, "upgrade": False}, + {"mode": "by_family", "top": 3, "names": ["gym"], "sort": [{"iv": 0.9}, {"ncp": 0.9}], "evolve": True, "upgrade": True}]) if (not self.config_may_use_lucky_egg) and self.config_evolve_only_with_lucky_egg: self.config_evolve = False @@ -52,7 +62,7 @@ def get_pokemon_slot_left(self): if pokemon_count != self.last_pokemon_count: self.last_pokemon_count = pokemon_count self.logger.info("Pokemon Bag: %s/%s", pokemon_count, self.max_pokemon_storage) - self.save_web_inventory() + inventory.update_web_inventory() return inventory.Pokemons.get_space_left() @@ -62,237 +72,396 @@ def work(self): self.open_inventory() + try_evolve_all = [] + try_upgrade_all = [] + keep_all = [] + + for rule in self.config_keep: + mode = rule.get("mode", "by_family") + names = rule.get("names", []) + whitelist_names, blacklist_names = self.get_colorlist_names(names) + + if mode == "by_pokemon": + for pokemon_id, pokemon_list in self.group_by_pokemon_id(inventory.pokemons().all()): + name = inventory.pokemons().name_for(pokemon_id) + + if name in blacklist_names: + continue + + if whitelist_names and (name not in whitelist_names): + continue + + try_evolve, try_upgrade, keep = self.get_best_pokemon_for_rule(pokemon_list, rule) + try_evolve_all += try_evolve + try_upgrade_all += try_upgrade + keep_all += keep + elif mode == "by_family": + for family_id, pokemon_list in self.group_by_family_id(inventory.pokemons().all()): + matching_names = self.get_family_names(family_id) + + if any(n in blacklist_names for n in matching_names): + continue + + if whitelist_names and not any(n in whitelist_names for n in matching_names): + continue + + if family_id == 133: # "Eevee" + try_evolve, try_upgrade, keep = self.get_multi_best_pokemon_for_rule(pokemon_list, rule, 3) + else: + try_evolve, try_upgrade, keep = self.get_best_pokemon_for_rule(pokemon_list, rule) + + try_evolve_all += try_evolve + try_upgrade_all += try_upgrade + keep_all += keep + elif mode == "overall": + pokemon_list = [] + + for pokemon in inventory.pokemons().all(): + name = pokemon.name + + if name in blacklist_names: + continue + + if whitelist_names and (name not in whitelist_names): + continue + + pokemon_list.append(pokemon) + + try_evolve, try_upgrade, keep = self.get_best_pokemon_for_rule(pokemon_list, rule) + try_evolve_all += try_evolve + try_upgrade_all += try_upgrade + keep_all += keep + + try_evolve_all = self.unique_pokemon_list(try_evolve_all) + try_upgrade_all = self.unique_pokemon_list(try_upgrade_all) + keep_all = self.unique_pokemon_list(keep_all) + transfer_all = [] - evo_all_best = [] - evo_all_crap = [] + evolve_all = [] + upgrade_all = [] + xp_all = [] - for family_id, family in self.family_by_family_id.items(): - if family_id == 133: # "Eevee" - transfer, evo_best, evo_crap = self.get_multi_family_optimized(family_id, family, 3) - else: - transfer, evo_best, evo_crap = self.get_family_optimized(family_id, family) + for family_id, pokemon_list in self.group_by_family_id(inventory.pokemons().all()): + try_evolve = [p for p in try_evolve_all if self.get_family_id(p) == family_id] + try_upgrade = [p for p in try_upgrade_all if self.get_family_id(p) == family_id] + keep = [p for p in keep_all if self.get_family_id(p) == family_id] - transfer_all += transfer - evo_all_best += evo_best - evo_all_crap += evo_crap + transfer, evolve, upgrade, xp = self.get_evolution_plan(family_id, pokemon_list, try_evolve, try_upgrade, keep) - evo_all = evo_all_best + evo_all_crap + transfer_all += transfer + evolve_all += evolve + upgrade_all += upgrade + xp_all += xp - self.apply_optimization(transfer_all, evo_all) - self.save_web_inventory() + self.apply_optimization(transfer_all, evolve_all, upgrade_all, xp_all) + inventory.update_web_inventory() return WorkerResult.SUCCESS def open_inventory(self): - self.family_by_family_id.clear() - for pokemon in inventory.pokemons().all(): - family_id = pokemon.first_evolution_id setattr(pokemon, "ncp", pokemon.cp_percent) setattr(pokemon, "dps", pokemon.moveset.dps) setattr(pokemon, "dps_attack", pokemon.moveset.dps_attack) setattr(pokemon, "dps_defense", pokemon.moveset.dps_defense) - self.family_by_family_id.setdefault(family_id, []).append(pokemon) + self.stardust_count = self.get_stardust_count() + self.ongoing_stardust_count = self.stardust_count - def save_web_inventory(self): - inventory.update_web_inventory() + def get_colorlist_names(self, names): + whitelist_names = [] + blacklist_names = [] - def get_family_optimized(self, family_id, family): - evolve_best = [] - keep_best = [] - family_names = self.get_family_names(family_id) + for name in names: + if not name: + continue - for criteria in self.config_keep: - names = criteria.get("names", []) + if name[0] not in ['!', '-']: + group_names = self.config_groups.get(name, []) - if names and not any(n in family_names for n in names): - continue + if not group_names: + name = self.get_closest_name(name) - if criteria.get("evolve", True): - evolve_best += self.get_top_rank(family, criteria) + if name: + whitelist_names.append(name) + whitelist_subnames, blacklist_subnames = self.get_colorlist_names(group_names) + whitelist_names += whitelist_subnames + blacklist_names += blacklist_subnames else: - keep_best += self.get_top_rank(family, criteria) + name = name[1:] + group_names = self.config_groups.get(name, []) - evolve_best = self.unique_pokemon(evolve_best) - keep_best = self.unique_pokemon(keep_best) + if not group_names: + name = self.get_closest_name(name) - return self.get_evolution_plan(family_id, family, evolve_best, keep_best) + if name: + blacklist_names.append(name) + blacklist_subnames, whitelist_subnames = self.get_colorlist_names(group_names) + blacklist_names += blacklist_subnames + whitelist_names += whitelist_subnames - def get_multi_family_optimized(self, family_id, family, nb_branch): - # Transfer each group of senior independently - senior_family = [p for p in family if not p.has_next_evolution()] - other_family = [p for p in family if p.has_next_evolution()] - senior_pids = set(p.pokemon_id for p in senior_family) - senior_grouped_family = {pid: [p for p in senior_family if p.pokemon_id == pid] for pid in senior_pids} + return (whitelist_names, blacklist_names) - if not self.config_evolve: - transfer, evo_best, evo_crap = self.get_family_optimized(family_id, other_family) - elif len(senior_pids) < nb_branch: - # We did not get every combination yet = All other Pokemon are potentially good to keep - transfer, evo_best, evo_crap = self.get_evolution_plan(family_id, [], other_family, []) - evo_best.sort(key=lambda p: p.iv * p.ncp, reverse=True) - else: - evolve_best = [] - keep_best = [] - names = self.get_family_names(family_id) + def get_family_names(self, family_id): + ids = [family_id] + ids += inventory.pokemons().data_for(family_id).next_evolutions_all[:] + return [inventory.pokemons().name_for(x) for x in ids] - for criteria in self.config_keep: - family_names = criteria.get("names", []) + def get_closest_name(self, name): + closest_names = difflib.get_close_matches(name, self.pokemon_names, 1) - if names and not any(n in family_names for n in names): - continue + if closest_names: + closest_name = closest_names[0] - top = [] + if name != closest_name: + self.logger.warning("Unknown Pokemon name [%s]. Assuming it is [%s]", name, closest_name) - for f in senior_grouped_family.values(): - top += self.get_top_rank(f, criteria) + return closest_name + else: + self.logger.error("Unknown Pokemon name [%s]", name) + return "" - worst = self.get_sorted_family(top, criteria)[-1] + def group_by_pokemon_id(self, pokemon_list): + sorted_list = sorted(pokemon_list, key=self.get_pokemon_id) + return itertools.groupby(sorted_list, self.get_pokemon_id) - if criteria.get("evolve", True): - evolve_best += self.get_better_rank(family, criteria, worst) - else: - keep_best += self.get_better_rank(family, criteria, worst) + def group_by_family_id(self, pokemon_list): + sorted_list = sorted(pokemon_list, key=self.get_family_id) + return itertools.groupby(sorted_list, self.get_family_id) - evolve_best = self.unique_pokemon(evolve_best) - keep_best = self.unique_pokemon(keep_best) - transfer, evo_best, evo_crap = self.get_evolution_plan(family_id, other_family, evolve_best, keep_best) + def get_pokemon_id(self, pokemon): + return pokemon.pokemon_id - for senior_pid, senior_family in senior_grouped_family.items(): - transfer += self.get_family_optimized(senior_pid, senior_family)[0] + def get_family_id(self, pokemon): + return pokemon.first_evolution_id - return (transfer, evo_best, evo_crap) + def get_best_pokemon_for_rule(self, pokemon_list, rule): + sorted_pokemon = self.sort_pokemon_list(pokemon_list, rule) - def get_family_names(self, family_id): - ids = [family_id] - ids += inventory.Pokemons.data_for(family_id).next_evolutions_all[:] - datas = [inventory.Pokemons.data_for(x) for x in ids] - return [x.name for x in datas] + if len(sorted_pokemon) == 0: + return ([], [], []) + + top = max(rule.get("top", 1), 0) + index = int(math.ceil(top)) - 1 - def get_top_rank(self, family, criteria): - sorted_family = self.get_sorted_family(family, criteria) - index = criteria.get("top", 1) - 1 + if 0 < top < 1: + worst = object() - 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)] + for a in rule.get("sort"): + best_attribute = getattr(sorted_pokemon[0], a) + setattr(worst, a, best_attribute * (1 - top)) + elif 0 <= index < len(sorted_pokemon): + worst = sorted_pokemon[index] else: - return sorted_family + worst = sorted_pokemon[-1] - 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)] + return self.get_better_pokemon_for_rule(sorted_pokemon, rule, worst) - def get_sorted_family(self, family, criteria): - return sorted(family, key=lambda p: self.get_rank(p, criteria), reverse=True) + def get_multi_best_pokemon_for_rule(self, family_list, rule, nb_branch): + sorted_family = self.sort_pokemon_list(family_list, rule) - def get_rank(self, pokemon, criteria): - return tuple(getattr(pokemon, a, None) for a in criteria.get("sort")) + # Handle each group of senior independently + senior_pokemon_list = [p for p in sorted_family if not p.has_next_evolution()] + other_family_list = [p for p in sorted_family if p.has_next_evolution()] + senior_pids = set(p.pokemon_id for p in senior_pokemon_list) - def get_pokemon_max_cp(self, pokemon_name): - return int(self.pokemon_max_cp.get(pokemon_name, 0)) + try_evolve_all = [] + try_upgrade_all = [] + keep_all = [] - def unique_pokemon(self, l): + if not self.config_evolve: + # Player handle evolution manually = Fall-back to per Pokemon behavior + for _, pokemon_list in self.group_by_pokemon_id(sorted_family): + try_evolve, try_upgrade, keep = self.get_best_pokemon_for_rule(pokemon_list, rule) + try_evolve_all += try_evolve + try_upgrade_all += try_upgrade + keep_all += keep + elif len(senior_pids) < nb_branch: + # We did not get every combination yet = All other Pokemon are potentially good to keep + for _, pokemon_list in self.group_by_pokemon_id(senior_pokemon_list): + try_evolve, try_upgrade, keep = self.get_best_pokemon_for_rule(pokemon_list, rule) + try_evolve_all += try_evolve + try_upgrade_all += try_upgrade + keep_all += keep + + try_evolve, try_upgrade, keep = self.get_better_pokemon_for_rule(other_family_list, rule, other_family_list[-1]) + try_evolve_all += try_evolve + try_upgrade_all += try_upgrade + keep_all += keep + else: + best = [] + + for _, pokemon_list in self.group_by_pokemon_id(senior_pokemon_list): + try_evolve, try_upgrade, keep = self.get_best_pokemon_for_rule(pokemon_list, rule) + best += try_evolve + best += try_upgrade + best += keep + + worst = self.sort_pokemon_list(best, rule)[-1] + + try_evolve_all, try_upgrade_all, keep_all = self.get_better_pokemon_for_rule(sorted_family, rule, worst) + + return try_evolve_all, try_upgrade_all, keep_all + + def get_better_pokemon_for_rule(self, pokemon_list, rule, worst): + min_score = self.get_score(worst, rule)[0] + scored_list = [(p, self.get_score(p, rule)) for p in pokemon_list] + best = [x for x in scored_list if x[1][0] >= min_score] + try_evolve = [x[0] for x in best if x[1][1] is True] + try_upgrade = [x[0] for x in best if x[1][1] is False and x[1][2] is True] + keep = [x[0] for x in best] + + return try_evolve, try_upgrade, keep + + def sort_pokemon_list(self, pokemon_list, rule): + return sorted(pokemon_list, key=lambda p: self.get_score(p, rule)[0], reverse=True) + + def get_score(self, pokemon, rule): + score = [] + may_try_evolve = (getattr(pokemon, "has_next_evolution", False) and + pokemon.has_next_evolution() and + rule.get("evolve", True)) + may_try_upgrade = rule.get("upgrade", False) + + for a in rule.get("sort"): + if (type(a) is str) or (type(a) is unicode): + value = getattr(pokemon, a, 0) + score.append(value) + elif type(a) is dict: + value = getattr(pokemon, a.keys()[0], 0) + score.append(value) + may_try_evolve &= (value >= a.values()[0]) + may_try_upgrade &= (value >= a.values()[0]) + elif type(a) is list: + value = getattr(pokemon, a[0], 0) + score.append(value) + may_try_evolve &= (value >= a[1]) + may_try_upgrade &= (value >= a[1]) + + return tuple(score), may_try_evolve, may_try_upgrade + + def unique_pokemon_list(self, pokemon_list): seen = set() - return [p for p in l if not (p.unique_id in seen or seen.add(p.unique_id))] + return [p for p in pokemon_list if not (p.unique_id in seen or seen.add(p.unique_id))] - def get_evolution_plan(self, family_id, family, evolve_best, keep_best): + def get_evolution_plan(self, family_id, family_list, try_evolve, try_upgrade, keep): candies = inventory.candies().get(family_id).quantity # All the rest is crap, for now - crap = family[:] - crap = [p for p in crap if p not in evolve_best] - crap = [p for p in crap if p not in keep_best] + crap = list(family_list) + crap = [p for p in crap if p not in keep] crap = [p for p in crap if not p.in_fort and not p.is_favorite] - crap.sort(key=lambda p: p.iv * p.ncp, reverse=True) + crap.sort(key=lambda p: (p.iv, p.cp), reverse=True) # We will gain a candy whether we choose to transfer or evolve these Pokemon candies += len(crap) - # Let's see if we can evolve our best Pokemon - can_evolve_best = [] - - for pokemon in evolve_best: - if not pokemon.has_next_evolution(): - continue + evolve = [] + for pokemon in try_evolve: candies -= pokemon.evolution_cost if candies < 0: - break + continue candies += 1 - can_evolve_best.append(pokemon) + evolve.append(pokemon) + + upgrade = [] - # Not sure if the evo keep the same id - next_pid = pokemon.next_evolution_ids[0] - next_evo = copy.copy(pokemon) - next_evo.pokemon_id = next_pid - next_evo.static = inventory.pokemons().data_for(next_pid) - next_evo.name = inventory.pokemons().name_for(next_pid) - evolve_best.append(next_evo) + for pokemon in try_upgrade: + level = int(pokemon.level * 2) - 1 + + if level >= 80: + continue + + full_upgrade_candy_cost = 0 + full_upgrade_stardust_cost = 0 + + for i in range(level, 80): + upgrade_cost = self.pokemon_upgrade_cost[i - 1] + full_upgrade_candy_cost += upgrade_cost[0] + full_upgrade_stardust_cost += upgrade_cost[1] + + candies -= full_upgrade_candy_cost + self.ongoing_stardust_count -= full_upgrade_stardust_cost + + if (candies < 0) or (self.ongoing_stardust_count < 0): + continue + + upgrade.append(pokemon) if self.config_evolve_for_xp: # Compute how many crap we should keep if we want to batch evolve them for xp - junior_evolution_cost = inventory.pokemons().evolution_cost_for(family_id) + lowest_evolution_cost = inventory.pokemons().evolution_cost_for(family_id) - # transfer + keep_for_evo = len(crap) + # transfer + keep_for_xp = len(crap) # leftover_candies = candies - len(crap) + transfer * 1 - # keep_for_evo = (leftover_candies - 1) / (junior_evolution_cost - 1) - # keep_for_evo = (candies - len(crap) + transfer - 1) / (junior_evolution_cost - 1) - # keep_for_evo = (candies - keep_for_evo - 1) / (junior_evolution_cost - 1) + # keep_for_xp = (leftover_candies - 1) / (lowest_evolution_cost - 1) + # keep_for_xp = (candies - len(crap) + transfer - 1) / (lowest_evolution_cost - 1) + # keep_for_xp = (candies - keep_for_xp - 1) / (lowest_evolution_cost - 1) - if (candies > 0) and junior_evolution_cost: - keep_for_evo = int((candies - 1) / junior_evolution_cost) + if (candies > 0) and lowest_evolution_cost: + keep_for_xp = int((candies - 1) / lowest_evolution_cost) else: - keep_for_evo = 0 + keep_for_xp = 0 - evo_crap = [p for p in crap if p.has_next_evolution() and p.evolution_cost == junior_evolution_cost][:keep_for_evo] + xp = [p for p in crap if p.has_next_evolution() and p.evolution_cost == lowest_evolution_cost][:keep_for_xp] # If not much to evolve, better keep the candies - if len(evo_crap) < math.ceil(self.max_pokemon_storage * 0.01): - evo_crap = [] + if len(xp) < math.ceil(self.max_pokemon_storage * 0.02): + xp = [] - transfer = [p for p in crap if p not in evo_crap] + transfer = [p for p in crap if p not in xp] else: - evo_crap = [] + xp = [] transfer = crap - return (transfer, can_evolve_best, evo_crap) + return (transfer, evolve, upgrade, xp) - def apply_optimization(self, transfer, evo): + def apply_optimization(self, transfer, evolve, upgrade, xp): self.logger.info("Transferring %s Pokemon", len(transfer)) for pokemon in transfer: self.transfer_pokemon(pokemon) - if len(evo) == 0: - return + skip_evolve = False if self.config_evolve and self.config_may_use_lucky_egg and (not self.bot.config.test): lucky_egg = inventory.items().get(Item.ITEM_LUCKY_EGG.value) # @UndefinedVariable if lucky_egg.count == 0: if self.config_evolve_only_with_lucky_egg: + skip_evolve = True self.emit_event("skip_evolve", formatted="Skipping evolution step. No lucky egg available") - return - elif len(evo) < self.config_evolve_count_for_lucky_egg: + elif (len(evolve) + len(xp)) < self.config_evolve_count_for_lucky_egg: if self.config_evolve_only_with_lucky_egg: + skip_evolve = True self.emit_event("skip_evolve", - formatted="Skipping evolution step. Not enough Pokemon to evolve with lucky egg: %s/%s" % (len(evo), self.config_evolve_count_for_lucky_egg)) - return + formatted="Skipping evolution step. Not enough Pokemon to evolve with lucky egg: %s/%s" % (len(evolve) + len(xp), self.config_evolve_count_for_lucky_egg)) elif self.get_pokemon_slot_left() > 5: + skip_evolve = True self.emit_event("skip_evolve", - formatted="Waiting for more Pokemon to evolve with lucky egg: %s/%s" % (len(evo), self.config_evolve_count_for_lucky_egg)) - return + formatted="Waiting for more Pokemon to evolve with lucky egg: %s/%s" % (len(evolve) + len(xp), self.config_evolve_count_for_lucky_egg)) else: self.use_lucky_egg() - self.logger.info("Evolving %s Pokemon", len(evo)) + if not skip_evolve: + self.logger.info("Evolving %s Pokemon (the best)", len(evolve)) + + for pokemon in evolve: + self.evolve_pokemon(pokemon) + + self.logger.info("Evolving %s Pokemon (for xp)", len(xp)) + + for pokemon in xp: + self.evolve_pokemon(pokemon) - for pokemon in evo: - self.evolve_pokemon(pokemon) + self.logger.info("Upgrading %s Pokemon [%s stardust]", len(upgrade), self.stardust_count) + + for pokemon in upgrade: + self.upgrade_pokemon(pokemon) def transfer_pokemon(self, pokemon): if self.config_transfer and (not self.bot.config.test): @@ -370,7 +539,7 @@ def evolve_pokemon(self, pokemon): if self.config_evolve and (not self.bot.config.test): response_dict = self.bot.api.evolve_pokemon(pokemon_id=pokemon.unique_id) else: - response_dict = {"responses": {"EVOLVE_POKEMON": {"result": 1}}} + response_dict = {"responses": {"EVOLVE_POKEMON": {"result": SUCCESS}}} if not response_dict: return False @@ -411,6 +580,56 @@ def evolve_pokemon(self, pokemon): if db_result[0] == 1: db.execute("INSERT INTO evolve_log (pokemon, iv, cp) VALUES (?, ?, ?)", (pokemon.name, pokemon.iv, pokemon.cp)) - sleep(self.config_evolve_time) + sleep(self.config_evolve_time, 0.1) + + return True + + def upgrade_pokemon(self, pokemon): + level = int(pokemon.level * 2) - 1 + candy = inventory.candies().get(pokemon.pokemon_id) + + for i in range(level, 80): + upgrade_cost = self.pokemon_upgrade_cost[i - 1] + upgrade_candy_cost = upgrade_cost[0] + upgrade_stardust_cost = upgrade_cost[1] + + if self.config_upgrade and (not self.bot.config.test): + response_dict = self.bot.api.upgrade_pokemon(pokemon_id=pokemon.unique_id) + else: + response_dict = {"responses": {"UPGRADE_POKEMON": {"result": SUCCESS}}} + + if not response_dict: + return False + + result = response_dict.get("responses", {}).get("UPGRADE_POKEMON", {}).get("result", 0) + + if result != SUCCESS: + return False + + upgrade = response_dict.get("responses", {}).get("UPGRADE_POKEMON", {}).get("upgraded_pokemon", {}) + + if self.config_upgrade and (not self.bot.config.test): + candy.consume(upgrade_candy_cost) + self.stardust_count -= upgrade_stardust_cost + + self.emit_event("pokemon_upgraded", + formatted="Upgraded {pokemon} [IV {iv}] [CP {cp}] [{candy} candies] [{stardust} stardust]", + data={"pokemon": pokemon.name, + "iv": pokemon.iv, + "cp": pokemon.cp, + "candy": candy.quantity, + "stardust": self.stardust_count}) + + if self.config_upgrade and (not self.bot.config.test): + inventory.pokemons().remove(pokemon.unique_id) + + new_pokemon = inventory.Pokemon(upgrade) + inventory.pokemons().add(new_pokemon) + + action_delay(self.config_transfer_wait_min, self.config_transfer_wait_max) return True + + def get_stardust_count(self): + response_dict = self.bot.api.get_player() + return response_dict.get("responses", {}).get("GET_PLAYER", {}).get("player_data", {}).get("currencies", [{}, {}])[1].get("amount", 0) diff --git a/pokemongo_bot/cell_workers/spin_fort.py b/pokemongo_bot/cell_workers/spin_fort.py index 505152d2df..c4a73aec4d 100644 --- a/pokemongo_bot/cell_workers/spin_fort.py +++ b/pokemongo_bot/cell_workers/spin_fort.py @@ -3,6 +3,7 @@ import json import os +import sys import time from pgoapi.utilities import f2i @@ -92,6 +93,9 @@ def work(self): c = conn.cursor() c.execute("SELECT COUNT(name) FROM sqlite_master WHERE type='table' AND name='pokestop_log'") result = c.fetchone() + c.execute("SELECT DISTINCT COUNT(pokestop) FROM pokestop_log WHERE dated >= datetime('now','-1 day')") + if c.fetchone()[0]>=self.config.get('daily_spin_limit',2000): + sys.exit(str(self.config.get('daily_spin_limit',2000))+" Pokestop spin in 24 hours") while True: if result[0] == 1: conn.execute('''INSERT INTO pokestop_log (pokestop, exp, items) VALUES (?, ?, ?)''', (fort_name, str(experience_awarded), str(items_awarded))) diff --git a/pokemongo_bot/cell_workers/telegram_task.py b/pokemongo_bot/cell_workers/telegram_task.py old mode 100644 new mode 100755 index 5c2b60787e..441039769b --- a/pokemongo_bot/cell_workers/telegram_task.py +++ b/pokemongo_bot/cell_workers/telegram_task.py @@ -1,109 +1,25 @@ # -*- coding: utf-8 -*- -import telegram + +from datetime import datetime +from datetime import timedelta import os -import logging import json +import telegram from pokemongo_bot.base_task import BaseTask from pokemongo_bot.base_dir import _base_dir from pokemongo_bot.event_handlers import TelegramHandler -from pprint import pprint -import re - class FileIOException(Exception): pass class TelegramTask(BaseTask): SUPPORTED_TASK_API_VERSION = 1 - update_id = None - tbot = None def initialize(self): if not self.enabled: return - api_key = self.bot.config.telegram_token - if api_key == None: - self.emit_event( - 'config_error', - formatted='api_key not defined.' - ) - return - self.tbot = telegram.Bot(api_key) - if self.config.get('master',None): - self.bot.event_manager.add_handler(TelegramHandler(self.tbot,self.config.get('master',None),self.config.get('alert_catch'))) - try: - self.update_id = self.tbot.getUpdates()[0].update_id - except IndexError: - self.update_id = None + self.bot.event_manager.add_handler(TelegramHandler(self.bot, self.config)) def work(self): if not self.enabled: return - for update in self.tbot.getUpdates(offset=self.update_id, timeout=10): - self.update_id = update.update_id+1 - if update.message: - self.bot.logger.info("message from {} ({}): {}".format(update.message.from_user.username, update.message.from_user.id, update.message.text)) - if self.config.get('master',None) and self.config.get('master',None) not in [update.message.from_user.id, "@{}".format(update.message.from_user.username)]: - self.emit_event( - 'debug', - formatted="Master wrong: expecting {}, got {}({})".format(self.config.get('master',None), update.message.from_user.username, update.message.from_user.id)) - continue - else: - if not re.match(r'^[0-9]+$', "{}".format(self.config['master'])): # master was not numeric... - self.config['master'] = update.message.chat_id - idx = (i for i,v in enumerate(self.bot.event_manager._handlers) if type(v) is TelegramHandler).next() - self.bot.event_manager._handlers[idx] = TelegramHandler(self.tbot,self.config['master'], self.config.get('alert_catch')) - - - - if update.message.text == "/info": - stats = self._get_player_stats() - if stats: - with self.bot.database as conn: - cur = conn.cursor() - cur.execute("SELECT DISTINCT COUNT(encounter_id) FROM catch_log WHERE dated >= datetime('now','-1 day')") - catch_day = cur.fetchone()[0] - cur.execute("SELECT DISTINCT COUNT(pokestop) FROM pokestop_log WHERE dated >= datetime('now','-1 day')") - ps_day = cur.fetchone()[0] - res = ( - "*"+self.bot.config.username+"*", - "_Level:_ "+str(stats["level"]), - "_XP:_ "+str(stats["experience"])+"/"+str(stats["next_level_xp"]), - "_Pokemons Captured:_ "+str(stats["pokemons_captured"])+" ("+str(catch_day)+" _last 24h_)", - "_Poke Stop Visits:_ "+str(stats["poke_stop_visits"])+" ("+str(ps_day)+" _last 24h_)", - "_KM Walked:_ "+str(stats["km_walked"]) - ) - self.tbot.sendMessage(chat_id=update.message.chat_id, parse_mode='Markdown', text="\n".join(res)) - self.tbot.send_location(chat_id=update.message.chat_id, latitude=self.bot.api._position_lat, longitude=self.bot.api._position_lng) - else: - self.tbot.sendMessage(chat_id=update.message.chat_id, parse_mode='Markdown', text="Stats not loaded yet\n") - elif update.message.text == "/start" or update.message.text == "/help": - res = ( - "Commands: ", - "/info - info about bot" - ) - self.tbot.sendMessage(chat_id=update.message.chat_id, parse_mode='Markdown', text="\n".join(res)) - - def _get_player_stats(self): - """ - Helper method parsing the bot inventory object and returning the player stats object. - :return: The player stats object. - :rtype: dict - """ - web_inventory = os.path.join(_base_dir, "web", "inventory-%s.json" % self.bot.config.username) - - try: - with open(web_inventory, "r") as infile: - json_inventory = json.load(infile) - except ValueError: - # Unable to read json from web inventory - # File may be corrupt. Create a new one. - self.bot.logger.info('[x] Error while opening inventory file for read: %s' % e) - json_inventory = [] - except: - raise FileIOException("Unexpected error reading from {}".web_inventory) - - return next((x["inventory_item_data"]["player_stats"] - for x in json_inventory - if x.get("inventory_item_data", {}).get("player_stats", {})), - None) diff --git a/pokemongo_bot/cell_workers/update_live_stats.py b/pokemongo_bot/cell_workers/update_live_stats.py index 743e3b9f19..e6689e3e4c 100644 --- a/pokemongo_bot/cell_workers/update_live_stats.py +++ b/pokemongo_bot/cell_workers/update_live_stats.py @@ -10,6 +10,7 @@ from pokemongo_bot.worker_result import WorkerResult from pokemongo_bot.tree_config_builder import ConfigException from pokemongo_bot.base_dir import _base_dir +from pokemongo_bot import inventory # XP file import json @@ -163,14 +164,12 @@ def work(self): if not self._should_display(): return WorkerResult.SUCCESS - player_stats = self._get_player_stats() + player_stats = inventory.player().player_stats line = self._get_stats_line(player_stats) # If line is empty, it couldn't be generated. if not line: return WorkerResult.SUCCESS - - self.update_web_stats(player_stats) - + if self.terminal_title: self._update_title(line, _platform) @@ -209,7 +208,7 @@ def _log_on_terminal(self, stats): formatted="{stats}", data={ 'stats': stats, - 'stats_raw': self._get_stats(self._get_player_stats()) + 'stats_raw': self._get_stats(inventory.player().player_stats) } ) self._compute_next_update() @@ -332,7 +331,6 @@ def _get_stats_line(self, player_stats): # Gather stats values. metrics = self.bot.metrics - metrics.capture_stats() runtime = metrics.runtime() login = self.bot.config.username player_data = self.bot.player_data @@ -417,39 +415,4 @@ def get_stat(stat): line = ' | '.join(map(get_stat, self.displayed_stats)) return line - - def _get_player_stats(self): - """ - Helper method parsing the bot inventory object and returning the player stats object. - :return: The player stats object. - :rtype: dict - """ - # TODO : find a better solution than calling the api - return self.bot.metrics.player_stats - - def update_web_stats(self,player_data): - web_inventory = os.path.join(_base_dir, "web", "inventory-%s.json" % self.bot.config.username) - - try: - with open(web_inventory, "r") as infile: - json_stats = json.load(infile) - except (IOError, ValueError): - # Unable to read json from web inventory - # File may be corrupt. Create a new one. - self.bot.logger.info('[x] Error while opening inventory file for read: %s' % e, 'red') - json_stats = [] - except: - raise FileIOException("Unexpected error loading information from json.") - - json_stats = [x for x in json_stats if not x.get("inventory_item_data", {}).get("player_stats", None)] - - json_stats.append({"inventory_item_data": {"player_stats": player_data}}) - - try: - with open(web_inventory, "w") as outfile: - json.dump(json_stats, outfile) - except (IOError, ValueError): - self.bot.logger.info('[x] Error while opening inventory file for write: %s' % e, 'red') - pass - except: - raise FileIOException("Unexpected error writing to {}".web_inventory) + \ No newline at end of file diff --git a/pokemongo_bot/cell_workers/update_web_inventory.py b/pokemongo_bot/cell_workers/update_web_inventory.py index 02b82128f3..34c5186981 100644 --- a/pokemongo_bot/cell_workers/update_web_inventory.py +++ b/pokemongo_bot/cell_workers/update_web_inventory.py @@ -1,5 +1,8 @@ +import json +import os from pokemongo_bot.base_task import BaseTask from pokemongo_bot import inventory +from pokemongo_bot.base_dir import _base_dir class UpdateWebInventory(BaseTask): @@ -9,4 +12,4 @@ def initialize(self): pass def work(self): - inventory.update_web_inventory() + inventory.update_web_inventory() \ No newline at end of file diff --git a/pokemongo_bot/cell_workers/utils.py b/pokemongo_bot/cell_workers/utils.py index 452176e687..a5dc6b8888 100644 --- a/pokemongo_bot/cell_workers/utils.py +++ b/pokemongo_bot/cell_workers/utils.py @@ -111,14 +111,17 @@ def convert(distance, from_unit, to_unit): # Converts units return distance * conversions[from_unit][to_unit] -def dist_to_str(distance, unit): - return '{:.2f}{}'.format(distance, unit) +def dist_to_str(distance, unit, append_unit = True): + if append_unit: + return '{:.2f}{}'.format(distance, unit) + else: + return '{:.2f}'.format(distance) -def format_dist(distance, unit): +def format_dist(distance, unit, append_unit = True): # Assumes that distance is in meters and converts it to the given unit, then a formatted string is returned # Ex: format_dist(1500, 'km') returns the string "1.5km" - return dist_to_str(convert(distance, 'm', unit), unit) + return dist_to_str(convert(distance, 'm', unit), unit, append_unit) def getSeconds(strTime): diff --git a/pokemongo_bot/event_handlers/colored_logging_handler.py b/pokemongo_bot/event_handlers/colored_logging_handler.py index d4a9331f2d..dec6cd3a9c 100644 --- a/pokemongo_bot/event_handlers/colored_logging_handler.py +++ b/pokemongo_bot/event_handlers/colored_logging_handler.py @@ -53,6 +53,7 @@ class ColoredLoggingHandler(EventHandler): 'pokemon_nickname_invalid': 'red', 'pokemon_not_in_range': 'yellow', 'pokemon_release': 'green', + 'pokemon_upgraded': 'green', 'pokemon_vanished': 'red', 'pokestop_empty': 'yellow', 'pokestop_log': 'magenta', diff --git a/pokemongo_bot/event_handlers/telegram_handler.py b/pokemongo_bot/event_handlers/telegram_handler.py old mode 100644 new mode 100755 index 4052e8c4ea..bee593c899 --- a/pokemongo_bot/event_handlers/telegram_handler.py +++ b/pokemongo_bot/event_handlers/telegram_handler.py @@ -1,18 +1,111 @@ # -*- coding: utf-8 -*- from pokemongo_bot.event_manager import EventHandler +from pokemongo_bot.base_dir import _base_dir +import json +import os +import time +import telegram import thread import re DEBUG_ON = False +class FileIOException(Exception): + pass + +class TelegramClass: + + update_id = None + + def __init__(self, bot, master, pokemons): + self.bot = bot + self.master = master + self.pokemons = pokemons + self._tbot = None + + def sendMessage(self, chat_id=None, parse_mode='Markdown', text=None): + self._tbot.sendMessage(chat_id=chat_id, parse_mode=parse_mode, text=text) + + def connect(self): + self._tbot = telegram.Bot(self.bot.config.telegram_token) + try: + self.update_id = self._tbot.getUpdates()[0].update_id + except IndexError: + self.update_id = None + + def _get_player_stats(self): + web_inventory = os.path.join(_base_dir, "web", "inventory-%s.json" % self.bot.config.username) + try: + with open(web_inventory, "r") as infile: + json_inventory = json.load(infile) + except ValueError as exception: + self.bot.logger.info('[x] Error while opening inventory file for read: %s' % exception) + json_inventory = [] + except: + raise FileIOException("Unexpected error reading from {}".format(web_inventory)) + return next((x["inventory_item_data"]["player_stats"] + for x in json_inventory + if x.get("inventory_item_data", {}).get("player_stats", {})), + None) + + def run(self): + time.sleep(1) + while True: + try: + for update in self._tbot.getUpdates(offset=self.update_id, timeout=10): + self.update_id = update.update_id+1 + if update.message: + self.bot.logger.info("message from {} ({}): {}".format(update.message.from_user.username, update.message.from_user.id, update.message.text)) + if self.master and self.master not in [update.message.from_user.id, "@{}".format(update.message.from_user.username)]: + continue + if update.message.text == "/info": + stats = self._get_player_stats() + if stats: + with self.bot.database as conn: + cur = conn.cursor() + cur.execute("SELECT DISTINCT COUNT(encounter_id) FROM catch_log WHERE dated >= datetime('now','-1 day')") + catch_day = cur.fetchone()[0] + cur.execute("SELECT DISTINCT COUNT(pokestop) FROM pokestop_log WHERE dated >= datetime('now','-1 day')") + ps_day = cur.fetchone()[0] + res = ( + "*"+self.bot.config.username+"*", + "_Level:_ "+str(stats["level"]), + "_XP:_ "+str(stats["experience"])+"/"+str(stats["next_level_xp"]), + "_Pokemons Captured:_ "+str(stats["pokemons_captured"])+" ("+str(catch_day)+" _last 24h_)", + "_Poke Stop Visits:_ "+str(stats["poke_stop_visits"])+" ("+str(ps_day)+" _last 24h_)", + "_KM Walked:_ "+str(stats["km_walked"]) + ) + self._tbot.sendMessage(chat_id=update.message.chat_id, parse_mode='Markdown', text="\n".join(res)) + self._tbot.send_location(chat_id=update.message.chat_id, latitude=self.bot.api._position_lat, longitude=self.bot.api._position_lng) + else: + self._tbot.sendMessage(chat_id=update.message.chat_id, parse_mode='Markdown', text="Stats not loaded yet\n") + elif update.message.text == "/start" or update.message.text == "/help": + res = ( + "Commands: ", + "/info - info about bot" + ) + self._tbot.sendMessage(chat_id=update.message.chat_id, parse_mode='Markdown', text="\n".join(res)) + except telegram.error.NetworkError: + time.sleep(1) + except telegram.error.Unauthorized: + self.update_id += 1 + class TelegramHandler(EventHandler): - def __init__(self, tbot,master,pokemons): - self.tbot = tbot - self.master=master - self.pokemons=pokemons - self.whoami="TelegramHandler" + def __init__(self, bot, config): + self.bot = bot + self.tbot = None + self.master = config.get('master', None) + self.pokemons = config.get('alert_catch', {}) + self.whoami = "TelegramHandler" def handle_event(self, event, sender, level, formatted_msg, data): + if self.tbot is None: + try: + self.tbot = TelegramClass(self.bot, self.master, self.pokemons) + self.tbot.connect() + thread.start_new_thread(self.tbot.run) + except Exception as inst: + self.tbot = None if self.master: if not re.match(r'^[0-9]+$', str(self.master)): return @@ -23,7 +116,7 @@ def handle_event(self, event, sender, level, formatted_msg, data): elif event == 'pokemon_caught': if isinstance(self.pokemons, list): if data["pokemon"] in self.pokemons or "all" in self.pokemons: - msg = "Caught {} CP: {}, IV: {}".format(data["pokemon"],data["cp"],data["iv"]) + msg = "Caught {} CP: {}, IV: {}".format(data["pokemon"], data["cp"], data["iv"]) else: return else: @@ -34,7 +127,7 @@ def handle_event(self, event, sender, level, formatted_msg, data): else: return if (not "operator" in trigger or trigger["operator"] == "and") and data["cp"] >= trigger["cp"] and data["iv"] >= trigger["iv"] or ("operator" in trigger and trigger["operator"] == "or" and (data["cp"] >= trigger["cp"] or data["iv"] >= trigger["iv"])): - msg = "Caught {} CP: {}, IV: {}".format(data["pokemon"],data["cp"],data["iv"]) + msg = "Caught {} CP: {}, IV: {}".format(data["pokemon"], data["cp"], data["iv"]) else: return else: diff --git a/pokemongo_bot/inventory.py b/pokemongo_bot/inventory.py index 0459bd4d34..b73531b101 100644 --- a/pokemongo_bot/inventory.py +++ b/pokemongo_bot/inventory.py @@ -1,6 +1,7 @@ import json import logging import os +import time from collections import OrderedDict from pokemongo_bot.base_dir import _base_dir @@ -14,6 +15,7 @@ https://www.reddit.com/r/pokemongodev/comments/4w7mdg/combat_damage_calculation_formula_exactly/ ''' + class FileIOException(Exception): pass @@ -22,8 +24,9 @@ class FileIOException(Exception): # Abstraction class _StaticInventoryComponent(object): - STATIC_DATA_FILE = None # optionally load static data from file, - # dropping the data in a static variable named STATIC_DATA + # optionally load static data from file, + # dropping the data in a static variable named STATIC_DATA + STATIC_DATA_FILE = None STATIC_DATA = None def __init__(self): @@ -80,6 +83,71 @@ def all(self): # # Inventory Components +class Player(_BaseInventoryComponent): + TYPE = 'player_stats' + + def __init__(self, bot, ttl=3): + self.bot = bot + self._exp = None + self._level = None + self.ttl = ttl + self.next_level_xp = None + self.pokemons_captured = None + self.poke_stop_visits = None + self.last_lvl_up_reward = time.time() # ts of last lvl_up_reward api call + self.player_stats = None + super(_BaseInventoryComponent, self).__init__() + + @property + def level(self): + return self._level + + @level.setter + def level(self, value): + if self._level != value: + now = time.time() + if now - self.last_lvl_up_reward > self.ttl: + self.bot.api.level_up_rewards(level=self.level) + + self._level = value + + @property + def exp(self): + return self._exp + + @exp.setter + def exp(self, value): + if self._exp != value: + now = time.time() + if now - self.last_lvl_up_reward > self.ttl: + self.bot.api.level_up_rewards(level=self.level) + + self._exp = value + + def refresh(self,inventory): + self.player_stats = self.retrieve_data(inventory) + + def parse(self, item): + if not item: + item = {} + + self.exp = item.get('experience', 0) + self.level = item.get('level', 0) + self.next_level_xp = item.get('next_level_xp', 0) + self.pokemons_captured = item.get('pokemons_captured', 0) + self.poke_stop_visits = item.get('poke_stop_visits', 0) + + def retrieve_data(self, inventory): + ret = {} + for item in inventory: + data = item['inventory_item_data'] + if self.TYPE in data: + item = data[self.TYPE] + ret = item + self.parse(item) + + return ret + class Candies(_BaseInventoryComponent): TYPE = 'candy' @@ -626,6 +694,7 @@ class PokemonInfo(object): """ Static information about pokemon kind """ + def __init__(self, data): self._data = data self.id = int(data["Number"]) @@ -1108,19 +1177,21 @@ def __init__(self, bot): self.candy = Candies() self.items = Items() self.pokemons = Pokemons() + self.player = Player(self.bot) # include inventory inside Player? self.refresh() self.item_inventory_size = None self.pokemon_inventory_size = None - def refresh(self): - inventory = self.bot.api.get_inventory() + def refresh(self, inventory=None): + if inventory is None: + inventory = self.bot.api.get_inventory() + inventory = inventory['responses']['GET_INVENTORY']['inventory_delta']['inventory_items'] - for i in (self.pokedex, self.candy, self.items, self.pokemons): + for i in (self.pokedex, self.candy, self.items, self.pokemons, self.player): i.refresh(inventory) self.update_web_inventory() - def init_inventory_outfile(self): web_inventory = os.path.join(_base_dir, "web", "inventory-%s.json" % self.bot.config.username) @@ -1128,39 +1199,22 @@ def init_inventory_outfile(self): self.bot.logger.info('No inventory file %s found. Creating a new one' % web_inventory) json_inventory = [] - + with open(web_inventory, "w") as outfile: json.dump(json_inventory, outfile) - def update_web_inventory(self): web_inventory = os.path.join(_base_dir, "web", "inventory-%s.json" % self.bot.config.username) if not os.path.exists(web_inventory): self.init_inventory_outfile() - - try: - with open(web_inventory, "r") as infile: - json_inventory = json.load(infile) - except (IOError, ValueError): - # Unable to read json from web inventory - # File may be corrupt. Create a new one. - self.bot.logger.info('[x] Error while opening inventory file for read: %s' % e, 'red') - json_inventory = [] - except: - raise FileIOException("Unexpected error reading from {}".web_inventory) - json_inventory = [x for x in json_inventory if not x.get("inventory_item_data", {}).get("pokedex_entry", None)] - json_inventory = [x for x in json_inventory if not x.get("inventory_item_data", {}).get("candy", None)] - json_inventory = [x for x in json_inventory if not x.get("inventory_item_data", {}).get("item", None)] - json_inventory = [x for x in json_inventory if not x.get("inventory_item_data", {}).get("pokemon_data", None)] - - json_inventory = json_inventory + self.jsonify_inventory() + json_inventory = self.jsonify_inventory() try: with open(web_inventory, "w") as outfile: json.dump(json_inventory, outfile) - except (IOError, ValueError): + except (IOError, ValueError) as e: self.bot.logger.info('[x] Error while opening inventory file for write: %s' % e, 'red') pass except: @@ -1169,6 +1223,8 @@ def update_web_inventory(self): def jsonify_inventory(self): json_inventory = [] + json_inventory.append({"inventory_item_data": {"player_stats": self.player.player_stats}}) + for pokedex in self.pokedex.all(): json_inventory.append({"inventory_item_data": {"pokedex_entry": pokedex}}) @@ -1180,9 +1236,9 @@ def jsonify_inventory(self): for pokemon in self.pokemons.all(): json_inventory.append({"inventory_item_data": {"pokemon_data": pokemon._data}}) - + return json_inventory - + def retrieve_inventories_size(self): """ Retrieves the item inventory size @@ -1273,17 +1329,28 @@ def init_inventory(bot): _inventory = Inventory(bot) -def refresh_inventory(): +def refresh_inventory(data=None): """ Refreshes the cached inventory, retrieves data from the server. :return: Nothing. :rtype: None """ - _inventory.refresh() - + try: + _inventory.refresh(data) + except AttributeError: + print '_inventory was not initialized' + +def jsonify_inventory(): + try: + return _inventory.jsonify_inventory() + except AttributeError: + print '_inventory was not initialized' + return [] + def update_web_inventory(): _inventory.update_web_inventory() + def get_item_inventory_size(): """ Access to the Item inventory size. @@ -1293,6 +1360,7 @@ def get_item_inventory_size(): _inventory.retrieve_inventories_size() return _inventory.item_inventory_size + def get_pokemon_inventory_size(): """ Access to the Item inventory size. @@ -1302,6 +1370,7 @@ def get_pokemon_inventory_size(): _inventory.retrieve_inventories_size() return _inventory.pokemon_inventory_size + def pokedex(): """ @@ -1312,6 +1381,10 @@ def pokedex(): return _inventory.pokedex +def player(): + return _inventory.player + + def candies(): """ diff --git a/pokemongo_bot/metrics.py b/pokemongo_bot/metrics.py index 693a357cfd..111dab9d26 100644 --- a/pokemongo_bot/metrics.py +++ b/pokemongo_bot/metrics.py @@ -1,6 +1,7 @@ import time from datetime import timedelta -from pokemongo_bot.inventory import Pokemons +from pokemongo_bot.inventory import Pokemons, refresh_inventory +from pokemongo_bot import inventory class Metrics(object): @@ -24,9 +25,8 @@ def __init__(self, bot): self.uniq_pokemons_caught = None self.uniq_pokemons_list = None - + self.player_stats = [] - self.inventory_data = [] def runtime(self): return timedelta(seconds=round(time.time() - self.start_time)) @@ -104,16 +104,19 @@ def capture_stats(self): except AttributeError: return - request.get_inventory() request.get_player() response_dict = request.call() + try: uniq_pokemon_list = set() self.dust['latest'] = response_dict['responses']['GET_PLAYER']['player_data']['currencies'][1]['amount'] - if self.dust['start'] < 0: self.dust['start'] = self.dust['latest'] + if self.dust['start'] < 0: + self.dust['start'] = self.dust['latest'] - for item in response_dict['responses']['GET_INVENTORY']['inventory_delta']['inventory_items']: + inventory.refresh_inventory() + json_inventory = inventory.jsonify_inventory() + for item in json_inventory: if 'inventory_item_data' in item: if 'player_stats' in item['inventory_item_data']: playerdata = item['inventory_item_data']['player_stats'] diff --git a/pokemongo_bot/sleep_schedule.py b/pokemongo_bot/sleep_schedule.py index bfa4e044fb..3ce10e24ef 100644 --- a/pokemongo_bot/sleep_schedule.py +++ b/pokemongo_bot/sleep_schedule.py @@ -101,9 +101,10 @@ def _schedule_next_sleep(self): self.bot.event_manager.emit( 'next_sleep', sender=self, - formatted="Next sleep at {time}", + formatted="Next sleep at {time}, for a duration of {duration}", data={ - 'time': str(self._next_sleep) + 'time': str(self._next_sleep.strftime("%H:%M:%S")), + 'duration': str(timedelta(seconds=self._next_duration)) } ) diff --git a/run.sh b/run.sh index c0179885a5..dac6c19484 100755 --- a/run.sh +++ b/run.sh @@ -3,38 +3,39 @@ pokebotpath=$(cd "$(dirname "$0")"; pwd) auth="" config="" if [ ! -z $1 ]; then -auth=$1 + config=$1 else -auth="./configs/auth.json" + config="./configs/config.json" fi if [ ! -z $2 ]; then -config=$2 + auth=$2 else -config="./configs/config.json" + auth="./configs/auth.json" fi cd $pokebotpath source bin/activate git fetch -a -if [ "1" == $(git branch -vv |grep -c "* dev") ] && [ $(git log --pretty=format:"%h" -1) != $(git log --pretty=format:"%h" -1 origin/dev) ] +if [ "1" == $(git branch -vv |grep -c "* dev") ] && [ $(git log --pretty=format:"%h" -1) != $(git log --pretty=format:"%h" -1 origin/dev) ] || + [ "1" == $(git branch -vv |grep -c "* master") ] && [ $(git log --pretty=format:"%h" -1) != $(git log --pretty=format:"%h" -1 origin/master) ] then -echo "Branch dev have an update. Run ./setup.sh -u to update." -sleep 2 -elif [ "1" == $(git branch -vv |grep -c "* master") ] && [ $(git log --pretty=format:"%h" -1) != $(git log --pretty=format:"%h" -1 origin/master) ] -then -echo "Branch master have an update. Run ./setup.sh -u to update." -sleep 2 + read -p "Branch has an update. Run ./setup.sh -u to update? y/n + " do_setup + if [[ $do_setup = "y" || $do_setup = "Y" ]]; + then + ./setup.sh -u + fi fi if [ ! -f "$auth" ]; then -echo "There's no auth file. Please use ./setup.sh -a to create one" + echo "There's no auth file. Please use ./setup.sh -a to create one" fi if [ ! -f "$config" ]; then -echo "There's no config file. Please use ./setup.sh -c to create one." + echo "There's no config file. Please use ./setup.sh -c to create one." fi while true do -python pokecli.py -af $auth -cf $config -echo `date`" Pokebot "$*" Stopped." -read -p "Press any button or wait 20 seconds to continue. -" -r -s -n1 -t 20 + python pokecli.py -af $auth -cf $config + echo `date`" Pokebot "$*" Stopped." + read -p "Press any button or wait 20 seconds to continue. + " -r -s -n1 -t 20 done exit 0 diff --git a/tests/inventory_test.py b/tests/inventory_test.py index 4070344ca4..40b3734207 100644 --- a/tests/inventory_test.py +++ b/tests/inventory_test.py @@ -162,7 +162,7 @@ def test_levels_to_cpm(self): max_cpm = l2c.cp_multiplier_for(l2c.MAX_LEVEL) self.assertEqual(l2c.MAX_LEVEL, 40) self.assertEqual(l2c.MAX_CPM, max_cpm) - self.assertEqual(len(l2c.STATIC_DATA), 79) + self.assertEqual(len(l2c.STATIC_DATA), 80) self.assertEqual(l2c.cp_multiplier_for("1"), 0.094) self.assertEqual(l2c.cp_multiplier_for(1), 0.094) diff --git a/web b/web index 6ba5609c61..e9322cd895 160000 --- a/web +++ b/web @@ -1 +1 @@ -Subproject commit 6ba5609c6151507b5b832a74e471b6b7b1a182c9 +Subproject commit e9322cd8952bcc1bbd70e302d3943d0abe5d3a00 diff --git a/windows_bat/PokemonGo-Bot-Install.bat b/windows_bat/PokemonGo-Bot-Install.bat index b744fc4586..2d8097a7e9 100755 --- a/windows_bat/PokemonGo-Bot-Install.bat +++ b/windows_bat/PokemonGo-Bot-Install.bat @@ -62,10 +62,9 @@ ECHO.>>%log% :CheckInstallPath cls -@ECHO.--------------------Installation Path-------------------- -@ECHO. -@ECHO. -@ECHO. +call:ech +ECHO.--------------------Installation Path-------------------- +call:ech SET InstallPath= SET /p InstallPath= "Choose an installation folder (example: C:/Test/) or press Enter to close: " ||goto:eof ECHO.----- Input Path ----->>%log% @@ -101,37 +100,30 @@ COPY "%PGBotPath%\windows_bat\PokemonGo-Bot-Install.bat" %InstallPath% CALL "%InstallPath%PokemonGo-Bot-Install.bat" exit. ) ELSE ( -@ECHO.Installation Path OK^^! Proceeding^^! +ECHO.Installation Path OK^^! Proceeding^^! ) ECHO.----- Checking Some Paths ----->>%log% ECHO.>>%log% ECHO.BatchPath is "%~dp0">>%log% ECHO.PokemonGo-Bot Path is "%PGBotPath%">>%log% ECHO.>>%log% -@ECHO. -@ECHO. -@ECHO. - :Menu cls -@ECHO.--------------------PokemonGo-Bot Installer-------------------- -@ECHO. -@ECHO. -@ECHO. +call:ech +ECHO.--------------------PokemonGo-Bot Installer-------------------- +call:ech if "%OS%" == "32-BIT" ( -@ECHO. 1 - Install 32-Bit software +ECHO. 1 - Install 32-Bit software ) ELSE ( -@ECHO. 1 - Install 64-Bit software +ECHO. 1 - Install 64-Bit software ) -@ECHO. -@ECHO. 2 - Install PokemonGo-Bot Master Version (Stable) -@ECHO. -@ECHO. 3 - Install PokemonGo-Bot Development Version (Unstable) -@ECHO. -@ECHO. -@ECHO. +ECHO. +ECHO. 2 - Install PokemonGo-Bot Master Version (Stable) +ECHO. +ECHO. 3 - Install PokemonGo-Bot Development Version (Unstable) +call:ech @@ -152,32 +144,27 @@ ECHO.>>%log% ECHO.Choice was "%_ok%" installing "%OS%" software>>%log% ECHO.>>%log% cls -@ECHO.--------------------Installation of required software-------------------- -@ECHO. -IF NOT EXIST %DownPath%\%GitFName86% @ECHO.Downloading Git... +call:ech +ECHO.--------------------Installation of required software-------------------- +call:ech +IF NOT EXIST %DownPath%\%GitFName86% ECHO.Downloading Git... IF NOT EXIST %DownPath%\%GitFName86% call:getit %DownPathGit% %GitFName86% -IF NOT EXIST %DownPath%\%PythonFName86% @ECHO.Downloading Python... +IF NOT EXIST %DownPath%\%PythonFName86% ECHO.Downloading Python... IF NOT EXIST %DownPath%\%PythonFName86% call:getit %DownPathPython% %PythonFName86% -IF NOT EXIST %DownPath%\%VisualFName% @ECHO.Downloading Visual C++ for Python... +IF NOT EXIST %DownPath%\%VisualFName% ECHO.Downloading Visual C++ for Python... IF NOT EXIST %DownPath%\%VisualFName% call:getit %DownPathVisual% %VisualFName% -@ECHO.Installing Python... +ECHO.Installing Python... IF EXIST %DownPath%\%PythonFName86% call:installit %PythonFName86% /quiet -@ECHO.Installing Git... +ECHO.Installing Git... IF EXIST %DownPath%\%GitFName86% call:installit %GitFName86% /SILENT -@ECHO.Installing Visual C++ for Python... +ECHO.Installing Visual C++ for Python... IF EXIST %DownPath%\%VisualFName% call:installit %VisualFName% /quiet -@ECHO. -@ECHO. -@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 +call:ech +ECHO.Installation of 32-Bit software is finished. +ECHO. +ECHO.Choose Install PokemonGo-Bot in next screen to complete installation. +call:ech +timeout /t 5 goto:Menu @@ -188,32 +175,28 @@ ECHO.>>%log% ECHO.Choice was "%_ok%" installing "%OS%" software>>%log% ECHO.>>%log% cls -@ECHO.--------------------Installation of required software-------------------- -@ECHO. -IF NOT EXIST %DownPath%\%GitFName64% @ECHO.Downloading Git... +call:ech +ECHO.--------------------Installation of required software-------------------- +call:ech +IF NOT EXIST %DownPath%\%GitFName64% ECHO.Downloading Git... IF NOT EXIST %DownPath%\%GitFName64% call:getit %DownPathGit% %GitFName64% -IF NOT EXIST %DownPath%\%PythonFName64% @ECHO.Downloading Python... +IF NOT EXIST %DownPath%\%PythonFName64% ECHO.Downloading Python... IF NOT EXIST %DownPath%\%PythonFName64% call:getit %DownPathPython% %PythonFName64% -IF NOT EXIST %DownPath%\%VisualFName% @ECHO.Downloading Visual C++ for Python... +IF NOT EXIST %DownPath%\%VisualFName% ECHO.Downloading Visual C++ for Python... IF NOT EXIST %DownPath%\%VisualFName% call:getit %DownPathVisual% %VisualFName% -@ECHO.Installing Python... +ECHO.Installing Python... IF EXIST %DownPath%\%PythonFName64% call:installit %PythonFName64% /quiet -@ECHO.Installing Git... +ECHO.Installing Git... IF EXIST %DownPath%\%GitFName64% call:installit %GitFName64% /SILENT -@ECHO.Installing Visual C++ for Python... +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 +call:ech +ECHO.Installation of 64-Bit software is finished. +ECHO. +ECHO.Choose Install PokemonGo-Bot in next screen to complete installation. +call:ech +ECHO. +timeout /t 5 goto:Menu @@ -243,10 +226,9 @@ ECHO.>>%log% ECHO.Choice was "%_ok%" installing Bot>>%log% ECHO.>>%log% cls -@ECHO.--------------------Creating Backup-------------------- -@ECHO. -@ECHO. -@ECHO. +call:ech +ECHO.--------------------Creating Backup-------------------- +call:ech ECHO.----- Checking Creating Backup ----->>%log% ECHO.>>%log% IF EXIST %PGBotPath%\encrypt.so ( @@ -267,6 +249,12 @@ COPY %PGBotPath%\encrypt_64.dll %DownPath%>>%log% ) else ( ECHO.No "%PGBotPath%\encrypt_64.dll">>%log% ) +IF EXIST %PGBotPath%\configs\auth.json ( +ECHO.Copying "%PGBotPath%\configs\auth.json" to %DownPath%>>%log% +COPY %PGBotPath%\configs\auth.json %DownPath%>>%log% +) else ( +ECHO.No "%PGBotPath%\configs\auth.json">>%log% +) IF EXIST %PGBotPath%\configs\config.json ( ECHO.Copying "%PGBotPath%\configs\config.json" to %DownPath%>>%log% COPY %PGBotPath%\configs\config.json %DownPath%>>%log% @@ -280,14 +268,12 @@ COPY %PGBotPath%\web\config\userdata.js %DownPath%>>%log% ECHO.No "%PGBotPath%\web\config\userdata.js">>%log% ) ECHO.>>%log% + cls -@ECHO. -@ECHO. -@ECHO. -@ECHO.--------------------Downloading PokemonGo-Bot-------------------- -@ECHO. -@ECHO. -@ECHO. +ECHO. +call:ech +ECHO.--------------------Downloading PokemonGo-Bot-------------------- +call:ech ECHO. Be patience... We are working hard to download and install the PokemonGo-Bot. ECHO.----- Checking Removing Bot Folder ----->>%log% ECHO.>>%log% @@ -319,13 +305,9 @@ ECHO.----- Checking second pip2 install or upgrade ----->>%log% ECHO.>>%log% pip2 install -r %PGBotPath%\requirements.txt>>%log% ECHO.>>%log% -@ECHO. -@ECHO. -@ECHO. -@ECHO.--------------------Restoring Backup-------------------- -@ECHO. -@ECHO. -@ECHO. +call:ech +ECHO.--------------------Restoring Backup-------------------- +call:ech ECHO.----- Checking Restoring Backup ----->>%log% ECHO.>>%log% IF EXIST %~dp0encrypt.so ( @@ -346,6 +328,12 @@ COPY %~dp0encrypt_64.dll %PGBotPath%>>%log% ) else ( ECHO.No "%~dp0encrypt_64.dll">>%log% ) +IF EXIST %~dp0auth.json ( +ECHO.Copying %~dp0auth.json to %PGBotPath%\configs\>>%log% +COPY %~dp0auth.json %PGBotPath%\configs\>>%log% +) else ( +ECHO.No "%~dp0auth.json">>%log% +) IF EXIST %~dp0config.json ( ECHO.Copying %~dp0config.json to %PGBotPath%\configs\>>%log% COPY %~dp0config.json %PGBotPath%\configs\>>%log% @@ -376,6 +364,12 @@ COPY %DownPath%\encrypt_64.dll %PGBotPath%>>%log% ) else ( ECHO.No "%DownPath%\encrypt_64.dll">>%log% ) +IF EXIST %DownPath%\auth.json ( +ECHO.Copying %DownPath%\auth.json to %PGBotPath%\configs\>>%log% +COPY %DownPath%\auth.json %PGBotPath%\configs\>>%log% +) else ( +ECHO.No "%DownPath%\auth.json">>%log% +) IF EXIST %DownPath%\config.json ( ECHO.Copying %DownPath%\config.json to %PGBotPath%\configs\>>%log% COPY %DownPath%\config.json %PGBotPath%\configs\>>%log% @@ -388,50 +382,57 @@ COPY %DownPath%\userdata.js %PGBotPath%\web\config\>>%log% ) else ( ECHO.No "%DownPath%\userdata.js">>%log% ) -@ECHO. -@ECHO. -@ECHO. ECHO.>>%Log% ECHO. ----- End Log ----->>%log% :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.--------------------File customization-------------------- +call:ech +ECHO.Before starting the bot, please copy the following files to %PGBotPath% if you dont have them yet. +call:ech IF NOT EXIST %PGBotPath%\encrypt*.* ( -@ECHO.---- encrypt.so / encrypt.dll or encrypt_64.dll -@ECHO. Get them from our Slack chat^^! -@ECHO. "https://pokemongo-bot.herokuapp.com/" +ECHO.---- encrypt.so / encrypt.dll or encrypt_64.dll +ECHO. Get them from our Slack chat^^! +ECHO. "https://pokemongo-bot.herokuapp.com/" ) else ( ECHO. ) -@ECHO. -@ECHO. -@ECHO.Remember to configure both config.json and userdata.js^^! -@ECHO. -@ECHO. -@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 Maps API Key: -@ECHO."https://developers.google.com/maps/documentation/javascript/get-api-key" -@ECHO. -@ECHO. -@ECHO. +call:ech +ECHO.Remember to configure auth.json, config.json and userdata.js^^! +call:ech +ECHO."%PGBotPath%\configs\auth.json" +ECHO."%PGBotPath%\configs\config.json" +ECHO.INSTRUCTIONS: +ECHO."https://github.com/PokemonGoF/PokemonGo-Bot/blob/master/docs/configuration_files.md" +call:ech +ECHO."%PGBotPath%\web\config\userdata.js" +ECHO.INSTRUCTIONS: +ECHO."https://github.com/PokemonGoF/PokemonGo-Bot/blob/master/docs/google_map.md" +call:ech +ECHO.To get an Google Maps API Key: +ECHO."https://developers.google.com/maps/documentation/javascript/get-api-key" +call:ech @PAUSE +CLS +call:ech +ECHO.You can configure the auth.json and userdata.js files when you choose y in the next question. +ECHO. +ECHO.If you first want to get your Google MAPS API key and encrypt dll/so choose n, +ECHO. +ECHO.put encrypt dll/so in %PGBotPath% and then, +ECHO. +ECHO.start PokemonGo-Bot-Configurator.bat in %PGBotPath%\windows_bat\ +call:ech +SET /p json="Do you want to start the PokemonGo-Bot-Configurator (Y/N): " +IF "%json%" == "y" call %PGBotPath%\windows_bat\PokemonGo-Bot-Configurator.bat +IF "%json%" == "n" goto:eof +:ech +ECHO. +ECHO. +goto:eof :eof ENDLOCAL From 8e8b4ab27aa9cc04d9d58ecbe8d6be9857bf1fac Mon Sep 17 00:00:00 2001 From: DBa2016 Date: Tue, 30 Aug 2016 23:05:45 +0200 Subject: [PATCH 2/4] Update merge (#2) * Expand simple logging options (#4832) * Fix bot crash at start on permaban * Expanded logging options Added "logging" section to config, with options "color", "show_datetime", "show_process_name" and "show_log_level" * Added warning about deprecated logging_color arg * Display log message moved No point trying to use the logger before it's been initialised. Moved to init_config. * Remove milliseconds from datetime Because really, do we need that? * Reversed condition order for clarity First check: "if not in config", OR Second check: "is in config AND set to true" If either condition matches, the logging detail will be displayed. * Documented new log options * Modified conditions again Removed unnecessary second check for config values and slightly modified parentheses as per suggestion from @mjmadsen * Changed ) to } (#4845) Fixed an faulty character * fix incubator logic (#4848) * corrected logic to respect snipe = true * Update configuration_files.md (#4854) * corrected logic to respect snipe = true (#4855) * Revert "corrected logic to respect snipe = true" (#4857) * dont forget to update the docs when adding config changes... (#4856) * dont forget to update the docs when adding config changes... * reflect config changes.... * please keep this as is (#4859) Add stuff in the right order. * Clarify Max_distance for Sniping (#4858) * Clarify Max_distance * Added distance unit and updated configuration_files.md * - debug improvements for MoveToMap (#4860) - fix for Telegram to accept "@username" as "master", too, along with numeric IDs * Fixes catch rates. (#4863) * Implemented more granularity in the "alert_catch" parameter for Telegram alerts. * Add exceptions to json file read/writes (#4877) * Fix bot crash at start on permaban * Expanded logging options Added "logging" section to config, with options "color", "show_datetime", "show_process_name" and "show_log_level" * Added warning about deprecated logging_color arg * Display log message moved No point trying to use the logger before it's been initialised. Moved to init_config. * Remove milliseconds from datetime Because really, do we need that? * Reversed condition order for clarity First check: "if not in config", OR Second check: "is in config AND set to true" If either condition matches, the logging detail will be displayed. * Documented new log options * Modified conditions again Removed unnecessary second check for config values and slightly modified parentheses as per suggestion from @mjmadsen * Add exception handling to json file read/write ops * Removed API call in update live stats Instead of making a new api call, utilise stats already contained in metrics. * Incubate eggs fix (#4881) * Fixed incubator_eggs wrong print * Fixed pokemon hatched from eggs not added to cached inventory * Fix * Fixed not using breakable incubators * Fixed error adding pokemon to cached inventory * Moved remove egg and add Pokemon to _hatch_eggs * add some sanitycheck (#4891) * execute setup.sh -u if there is a need to (#4870) * execute setup.sh -u if there is a need to * ask the user whether to run setup.sh -u or not * fix grammatical error * Add PokemonGo bot version to docker image (#4886) * fix pep8 * Add PokemonGo bot version to docker image * Use https://api.github.com/repos/PokemonGoF/PokemonGo-Bot/commits{/sha} API * Fix remove pyc, pyo files * Call level_up_rewards on exp changes/Some pep-8 (#4896) * Call level_up_rewards on exp changes. * Cleanup * Improvements to evolve + config md updates (#4900) * Better do not evolve handling * Edit config * Update config * Edit config * Edit config * Edit config * Update config.json.path.example * Update config.json.map.example * Update config.json.example * Update config.json.cluster.example * Updated configuration_files.md * Add extra tests * Update config * Update config * Update config * Update config * Update config.json.pokemon.example * Update config.json.cluster.example * Begin fixing configuration_files.md * Small fix * Small fix * Bit for of config updated * Bit more on config * A few more to config md * Bit more of of an update * 2000 pokestop in 24h limit (#4906) * 2000 pokestop in 24h limit * 2000 pokestop in 24h limit * add config variable * config update * Update readme.md + Improve FollowPath & SleepSchedule messages (#4911) * Use logger for follow path loiter message * Update readme.md * Improve sleep message * Allow follow_path to use config's distance unit * Allow follow_path to use config's distance unit * Reduce API calls (#4916) * Fix bot crash at start on permaban * Expanded logging options Added "logging" section to config, with options "color", "show_datetime", "show_process_name" and "show_log_level" * Added warning about deprecated logging_color arg * Display log message moved No point trying to use the logger before it's been initialised. Moved to init_config. * Remove milliseconds from datetime Because really, do we need that? * Reversed condition order for clarity First check: "if not in config", OR Second check: "is in config AND set to true" If either condition matches, the logging detail will be displayed. * Documented new log options * Modified conditions again Removed unnecessary second check for config values and slightly modified parentheses as per suggestion from @mjmadsen * Add exception handling to json file read/write ops * Removed API call in update live stats Instead of making a new api call, utilise stats already contained in metrics. * Update player data in web from metrics Uses existing metrics instead of waiting on liveupdate * Implemented more granularity in the "alert_catch" parameter for Telegram alerts. * Improvements to evolve + config md updates (#4900) * Better do not evolve handling * Edit config * Update config * Edit config * Edit config * Edit config * Update config.json.path.example * Update config.json.map.example * Update config.json.example * Update config.json.cluster.example * Updated configuration_files.md * Add extra tests * Update config * Update config * Update config * Update config * Update config.json.pokemon.example * Update config.json.cluster.example * Begin fixing configuration_files.md * Small fix * Small fix * Bit for of config updated * Bit more on config * A few more to config md * Bit more of of an update * Incubate eggs fix (#4881) * Fixed incubator_eggs wrong print * Fixed pokemon hatched from eggs not added to cached inventory * Fix * Fixed not using breakable incubators * Fixed error adding pokemon to cached inventory * Moved remove egg and add Pokemon to _hatch_eggs * Call level_up_rewards on exp changes/Some pep-8 (#4896) * Call level_up_rewards on exp changes. * Cleanup * add some sanitycheck (#4891) * execute setup.sh -u if there is a need to (#4870) * execute setup.sh -u if there is a need to * ask the user whether to run setup.sh -u or not * fix grammatical error * Add PokemonGo bot version to docker image (#4886) * fix pep8 * Add PokemonGo bot version to docker image * Use https://api.github.com/repos/PokemonGoF/PokemonGo-Bot/commits{/sha} API * Fix remove pyc, pyo files * Refactoring to share inventory and reduce api calls Modifications to share cached inventory and reduce overall required api calls from 4 to 1. Only remaining api call comes from heartbeat which updates the cached inventory for sanity reasons. * Remove import of UpdateWebPlayerdata Decided there was a better way to go with this, since both UpdateWebInventory and UpdateWebPlayerdata share the same inventory input/output, just different sections. Combined into UpdateWebInventory. * Fixed conflict * Import inventory added to metrics Allows metrics to use the cached inventory to retrieve player stats instead of making another api call * Removed api call from incubate_eggs Cached inventory should be accurate enough for this * Swap auth and config position (#4909) * add telegram check messages interval (#4919) * add telegram check messages interval * config changed * fix config * telegram doc update * Add documentation (#4921) See documentation for full list of new features * documented docker for the auth.json use case (#4922) add instructions for the docker run command for the multiple config files use case. * Fix for #4718 (#4924) * Add except variable * Add except variable * Fix filter (#4925) * improve docker pull speed (#4899) * Update inventory.py (#4928) FIX: #4926 * fixed a runtime error caused by incorrect imports (#4931) * Catch exception telegram.error.NetworkError. Fixs some pylint complain. * More config parameters for MoveToMap (#4937) * fixed a runtime error caused by incorrect imports * Moving module-level constants (snipe parameters) into config file * Add experimental pokemon upgrade (power-up) logic (#4938) Add upgrade cost data file Add last pokemon level. * Set default value of skip_rounds to 30 since many people just use the default value. 30 will behave close to human. * Add exception handling for cached forts (#4943) * Add exception handling for cached forts * whitespace fix * telegram to thread * config update * doc update * update web repo to have better web ui contribute. * Using $TIMEZONE environment variable to set timezone * fix errors * fix errors * Fixing clean run issues. * During startup, no bot object. * Added option PokemonGo-Bot-Configurator Smoothed some things up Added option to run PokemonGo-Bot-Configurator at the end of the installation. * Hotfix for EvolvePokemon (#4960) * Compatiable with old protocol define in map-chat. From 9989632474f6eb7afdccf3c21ed2ba28fda56906 Mon Sep 17 00:00:00 2001 From: DBa2016 Date: Thu, 1 Sep 2016 01:54:12 +0200 Subject: [PATCH 3/4] Provided a default for max_walking_distance and max_sniping_distance in order to avoid unhandled exceptions --- pokemongo_bot/cell_workers/move_to_map_pokemon.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/pokemongo_bot/cell_workers/move_to_map_pokemon.py b/pokemongo_bot/cell_workers/move_to_map_pokemon.py index 64ec72d8a2..4a4ece400a 100644 --- a/pokemongo_bot/cell_workers/move_to_map_pokemon.py +++ b/pokemongo_bot/cell_workers/move_to_map_pokemon.py @@ -138,13 +138,15 @@ def get_pokemon_from_social(self): pokemon['longitude'], ) - if pokemon['dist'] > self.config['max_sniping_distance'] and self.config['snipe']: + # If distance to pokemon greater than the max_sniping_distance, then ignore regardless of "snipe" setting + if pokemon['dist'] > self.config.get('max_sniping_distance', 10000): continue - - if pokemon['dist'] > self.config['max_walking_distance'] and not self.config['snipe']: + + # If distance bigger than walking distance, ignore if sniping is not active + if pokemon['dist'] > self.config.get('max_walking_distance', 1000) and not self.config.get('snipe', False): continue - # pokemon not reachable with mean walking speed (by config) + # if 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 From 07d162953a2abd40caa3c5d1a043cdfc323c2a58 Mon Sep 17 00:00:00 2001 From: DBa2016 Date: Thu, 1 Sep 2016 02:14:13 +0200 Subject: [PATCH 4/4] Made sure nicknaming only waits if real api-relevant action was taken. --- .../cell_workers/nickname_pokemon.py | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/pokemongo_bot/cell_workers/nickname_pokemon.py b/pokemongo_bot/cell_workers/nickname_pokemon.py index 5e47109e16..834347450b 100644 --- a/pokemongo_bot/cell_workers/nickname_pokemon.py +++ b/pokemongo_bot/cell_workers/nickname_pokemon.py @@ -4,7 +4,7 @@ import os import json 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 pokemons, Pokemon, Attack import re @@ -210,9 +210,9 @@ def work(self): for pokemon in pokemons().all(): # type: Pokemon if not pokemon.is_favorite or not self.ignore_favorites: if pokemon.iv >= self.nickname_above_iv: - # Make the bot appears more human - action_delay(self.nickname_wait_min, self.nickname_wait_max) - self._nickname_pokemon(pokemon) + if self._nickname_pokemon(pokemon): + # Make the bot appears more human + action_delay(self.nickname_wait_min, self.nickname_wait_max) def _localize(self, string): if self.translate and string in self.translate: @@ -221,7 +221,8 @@ def _localize(self, string): return string def _nickname_pokemon(self, pokemon): - # type: (Pokemon) -> None + # type: (Pokemon) -> bool + # returns False if no wait needed (no API calls tried before return), True if wait is needed """ Nicknaming process """ @@ -233,7 +234,7 @@ def _nickname_pokemon(self, pokemon): 'api_error', formatted='Failed to get pokemon name, will not rename.' ) - return + return False # Generate new nickname old_nickname = pokemon.nickname @@ -245,11 +246,11 @@ def _nickname_pokemon(self, pokemon): formatted="Unable to nickname {} due to bad template ({})" .format(old_nickname, bad_key) ) - return + return False # Skip if pokemon is already well named if pokemon.nickname_raw == new_nickname: - return + return False # Send request response = self.bot.api.nickname_pokemon( @@ -265,7 +266,7 @@ def _nickname_pokemon(self, pokemon): 'api_error', formatted='Attempt to nickname received bad response from server.' ) - return + return True # Nickname unset if result == 0: @@ -294,6 +295,7 @@ def _nickname_pokemon(self, pokemon): formatted='Attempt to nickname received unexpected result' ' from server ({}).'.format(result) ) + return True def _generate_new_nickname(self, pokemon, template): # type: (Pokemon, string) -> string