From ef913766f6af45cffd84fe6dcb7c187a91c5b60c Mon Sep 17 00:00:00 2001 From: MerlionRock Date: Wed, 18 Jan 2017 02:20:04 +0800 Subject: [PATCH] Feature Enhancement: Snip request through telegram (#5877) * Add files via upload * Add files via upload * Telegram snipe mode changes Make changes to ignore Pokemon in snipe catch list for telegram mode. * Catch exceptions To prevent looping errors due to wrong input by users, try except expression added to telegram snipe mode * Extra variable removed Traget = {} removed * Telegram Snipe Instructions Added instructions on how to activate and use manual snipping through telegram --- docs/configuration_files.md | 1 + pokemongo_bot/cell_workers/sniper.py | 108 ++++++++++++++---- .../event_handlers/telegram_handler.py | 34 ++++++ 3 files changed, 119 insertions(+), 24 deletions(-) diff --git a/docs/configuration_files.md b/docs/configuration_files.md index 2c1b663388..4e0e2c1ec9 100644 --- a/docs/configuration_files.md +++ b/docs/configuration_files.md @@ -774,6 +774,7 @@ This task is an upgrade version of the MoveToMapPokemon task. It will fetch poke * `mode` - The mode on which the sniper will fetch the informations. (default: social) - `social` - Information will come from the social network. - `url` - Information will come from one or multiple urls. + - `telegram` - Manual snipping through telegram. In telegram, use "/snipe " to snipe. Subscript to "/sub sniper_log" and "/sub pokemon_vip_caught" to retrieve snipping results through telegram. * `bullets` - Each bullet corresponds to an **ATTEMPT** of catching a pokemon. (default: 1) * `homing_shots` - This will ensure that each bullet **will catch** a target. If disabled, a target might not exist and thus it wont be caught. When enabled, this will jump to the next target (if any) and try again to catch it. This will be repeated untill you've spent all the bullets. (default: true) * `special_iv` - This will skip the catch list if the value is greater than or equal to the target's IV. This currently does not work with `social` mode and only works if the given `url` has this information. (default: 100) diff --git a/pokemongo_bot/cell_workers/sniper.py b/pokemongo_bot/cell_workers/sniper.py index 71786c5f4c..6c96fc25ef 100644 --- a/pokemongo_bot/cell_workers/sniper.py +++ b/pokemongo_bot/cell_workers/sniper.py @@ -10,11 +10,13 @@ from random import uniform from operator import itemgetter, methodcaller from datetime import datetime +from itertools import izip from pokemongo_bot import inventory from pokemongo_bot.item_list import Item from pokemongo_bot.base_task import BaseTask from pokemongo_bot.inventory import Pokemons from pokemongo_bot.worker_result import WorkerResult +from pokemongo_bot.event_handlers.telegram_handler import TelegramSnipe from pokemongo_bot.cell_workers.pokemon_catch_worker import PokemonCatchWorker # Represents a URL source and its mappings @@ -219,13 +221,14 @@ class SniperOrderMode(object): class SniperMode(object): URL = 'url' SOCIAL = 'social' + TELEGRAM = 'telegram' DEFAULT = SOCIAL # Teleports the player to a target gotten from either social or a single/multiple URL sources class Sniper(BaseTask): SUPPORTED_TASK_API_VERSION = 1 - MIN_SECONDS_ALLOWED_FOR_CELL_CHECK = 10 - MIN_SECONDS_ALLOWED_FOR_REQUESTING_DATA = 5 + MIN_SECONDS_ALLOWED_FOR_CELL_CHECK = 60 + MIN_SECONDS_ALLOWED_FOR_REQUESTING_DATA = 10 MIN_BALLS_FOR_CATCHING = 10 MAX_CACHE_LIST_SIZE = 300 @@ -239,7 +242,7 @@ def initialize(self): self.inventory = inventory.items() self.pokedex = inventory.pokedex() self.debug = self.config.get('debug', False) - self.special_iv = self.config.get('special_iv', 100) + self.special_iv = self.config.get('special_iv', 0) self.bullets = self.config.get('bullets', 1) self.homing_shots = self.config.get('homing_shots', True) self.mode = self.config.get('mode', SniperMode.DEFAULT) @@ -277,9 +280,13 @@ def initialize(self): self.sources.remove(source) # Notify user if all sources are invalid and cant proceed - if not self.sources: + if not self.sources : self._error("There is no source available. Disabling Sniper...") self.disabled = True + + # Re-enable snipping if source is from telegram + if self.mode == SniperMode.TELEGRAM: + self.disabled = False def is_snipeable(self, pokemon): pokeballs_count = self.inventory.get(Item.ITEM_POKE_BALL.value).count @@ -287,18 +294,19 @@ def is_snipeable(self, pokemon): ultraballs_count = self.inventory.get(Item.ITEM_ULTRA_BALL.value).count all_balls_count = pokeballs_count + greatballs_count + ultraballs_count - # Skip if expired (cast milliseconds to seconds for comparision) - if (pokemon.get('expiration_timestamp_ms', 0) or pokemon.get('last_modified_timestamp_ms', 0)) / 1000 < time.time(): - self._trace('{} is expired! Skipping...'.format(pokemon.get('pokemon_name'))) - return False + # Skip if expired (cast milliseconds to seconds for comparision), snipe check if source is from telegram + if self.mode != SniperMode.TELEGRAM: + if (pokemon.get('expiration_timestamp_ms', 0) or pokemon.get('last_modified_timestamp_ms', 0)) / 1000 < time.time(): + self._trace('{} is expired! Skipping...'.format(pokemon.get('pokemon_name'))) + return False # Skip if not enought balls. Sniping wastes a lot of balls. Theres no point to let user decide this amount if all_balls_count < self.MIN_BALLS_FOR_CATCHING: self._trace('Not enought balls left! Skipping...') return False - # Skip if not in catch list, not a VIP and/or IV sucks (if any) - if pokemon.get('pokemon_name', '') in self.catch_list: + # Skip if not in catch list, not a VIP and/or IV sucks (if any), ignore telegram mode + if pokemon.get('pokemon_name', '') in self.catch_list or self.mode == SniperMode.TELEGRAM: self._trace('{} is catchable!'.format(pokemon.get('pokemon_name'))) else: # Not catchable. Having a good IV should suppress the not in catch/vip list (most important) @@ -322,7 +330,7 @@ def snipe(self, pokemon): success = False # Apply snipping business rules and snipe if its good - if not self.is_snipeable(pokemon): + if not self.is_snipeable(pokemon) and not self.mode == SniperMode.TELEGRAM: self._trace('{} is not snipeable! Skipping...'.format(pokemon['pokemon_name'])) else: # Have we already tried this pokemon? @@ -341,32 +349,61 @@ def snipe(self, pokemon): # Teleport, so that we can see nearby stuff self.bot.hb_locked = True self._teleport_to(pokemon) + # If social is enabled and if no verification is needed, trust it. Otherwise, update IDs! verify = not pokemon.get('encounter_id') or not pokemon.get('spawn_point_id') exists = not verify or self.mode == SniperMode.SOCIAL success = exists + + # Always verify if it's from telegram + if TelegramSnipe.ENABLED == True: + verify = True # If information verification have to be done, do so if verify: seconds_since_last_check = time.time() - self.last_cell_check_time # Wait a maximum of MIN_SECONDS_ALLOWED_FOR_CELL_CHECK seconds before requesting nearby cells - if (seconds_since_last_check < self.MIN_SECONDS_ALLOWED_FOR_CELL_CHECK): - time.sleep(self.MIN_SECONDS_ALLOWED_FOR_CELL_CHECK - seconds_since_last_check) - + self._trace('Pausing for {} secs before checking for Pokemons'.format(self.MIN_SECONDS_ALLOWED_FOR_CELL_CHECK)) + + #recode it to check every 5 secs, first check for wild then catchable nearby_pokemons = [] - nearby_stuff = self.bot.get_meta_cell() - self.last_cell_check_time = time.time() - - # Retrieve nearby pokemons for validation - nearby_pokemons.extend(nearby_stuff.get('wild_pokemons', [])) - nearby_pokemons.extend(nearby_stuff.get('catchable_pokemons', [])) - + nearby_stuff = [] + num = 0 + for num in range(0,self.MIN_SECONDS_ALLOWED_FOR_CELL_CHECK): + if num%5 == 0: + nearby_stuff = self.bot.get_meta_cell() + self.last_cell_check_time = time.time() + + # Retrieve nearby pokemons for validation + nearby_pokemons.extend(nearby_stuff.get('wild_pokemons', [])) + if nearby_pokemons: + break + + time.sleep(1) + num += 1 + + num = 0 + for num in range(0,self.MIN_SECONDS_ALLOWED_FOR_CELL_CHECK): + if num%5 == 0: + nearby_stuff = self.bot.get_meta_cell() + self.last_cell_check_time = time.time() + + # Retrieve nearby pokemons for validation + nearby_pokemons.extend(nearby_stuff.get('catchable_pokemons', [])) + if nearby_pokemons: + break + + time.sleep(1) + num += 1 + + self._trace('Pokemon Nearby: {}'.format(nearby_pokemons)) + # Make sure the target really/still exists (nearby_pokemon key names are game-bound!) for nearby_pokemon in nearby_pokemons: nearby_pokemon_id = nearby_pokemon.get('pokemon_data', {}).get('pokemon_id') or nearby_pokemon.get('pokemon_id') - + # If we found the target, it exists and will very likely be encountered/caught (success) if nearby_pokemon_id == pokemon.get('pokemon_id', 0): exists = True @@ -382,9 +419,13 @@ def snipe(self, pokemon): if exists: self._log('Yay! There really is a wild {} nearby!'.format(pokemon.get('pokemon_name'))) self._teleport_back_and_catch(last_position, pokemon) + else: self._error('Damn! Its not here. Reasons: too far, caught, expired or fake data. Skipping...') self._teleport_back(last_position) + + #Set always to false to re-enable sniper to check for telegram data + TelegramSnipe.ENABLED = False # Save target and unlock heartbeat calls self._cache(uniqueid) @@ -393,6 +434,8 @@ def snipe(self, pokemon): return success def work(self): + #Check if telegram is called + # Do nothing if this task was invalidated if self.disabled: self._error("Sniper was disabled for some reason. Scroll up to find out.") @@ -412,6 +455,8 @@ def work(self): targets = self._get_pokemons_from_social() elif self.mode == SniperMode.URL: targets = self._get_pokemons_from_url() + elif self.mode == SniperMode.TELEGRAM and TelegramSnipe.ENABLED: + targets = self._get_pokemons_from_telegram() if targets: # Order the targets (descending) @@ -442,6 +487,9 @@ def work(self): if shots < self.bullets and index < len(targets): self._trace('Waiting a few seconds to teleport again to another target...') time.sleep(3) + + # Always set telegram back to false + TelegramSnipe.ENABLED = False return WorkerResult.SUCCESS @@ -450,7 +498,7 @@ def _parse_pokemons(self, pokemon_dictionary_list): # Build up the pokemon. Pops are used to destroy random attribute names and keep the known ones! for pokemon in pokemon_dictionary_list: - pokemon['iv'] = pokemon.get('iv', 0) + pokemon['iv'] = pokemon.get('iv', 100) pokemon['pokemon_name'] = pokemon.get('pokemon_name', Pokemons.name_for(pokemon.get('pokemon_id'))) pokemon['vip'] = pokemon.get('pokemon_name') in self.bot.config.vips pokemon['missing'] = not self.pokedex.captured(pokemon.get('pokemon_id')) @@ -459,8 +507,20 @@ def _parse_pokemons(self, pokemon_dictionary_list): # Check whether this is a valid target if self.is_snipeable(pokemon): result.append(pokemon) - + return result + + def _get_pokemons_from_telegram(self): + if not TelegramSnipe.ENABLED: + return {} + + pokemons = [] + pokemon = {'iv': int(0), 'pokemon_id': int(TelegramSnipe.ID), 'pokemon_name': str(TelegramSnipe.POKEMON_NAME), 'latitude': float(TelegramSnipe.LATITUDE), 'longitude': float(TelegramSnipe.LONGITUDE)} + self._log('Telegram snipe request: {}'.format(pokemon.get('pokemon_name'))) + + pokemons = [pokemon] + + return self._parse_pokemons(pokemons) def _get_pokemons_from_social(self): if not hasattr(self.bot, 'mqtt_pokemon_list') or not self.bot.mqtt_pokemon_list: diff --git a/pokemongo_bot/event_handlers/telegram_handler.py b/pokemongo_bot/event_handlers/telegram_handler.py index fdf8fcb060..8f661db066 100644 --- a/pokemongo_bot/event_handlers/telegram_handler.py +++ b/pokemongo_bot/event_handlers/telegram_handler.py @@ -6,9 +6,16 @@ import re from telegram.utils import request from chat_handler import ChatHandler +from pokemongo_bot.inventory import Pokemons DEBUG_ON = False +class TelegramSnipe(object): + ENABLED = False + ID = int(0) + POKEMON_NAME = '' + LATITUDE = float(0) + LONGITUDE = float(0) class TelegramClass: update_id = None @@ -187,7 +194,25 @@ def send_caught(self, update, num, order): else: self.sendMessage(chat_id=update.message.chat_id, parse_mode='Markdown', text="No Pokemon Caught Yet.\n") + + def request_snipe(self, update, pkm, lat, lng): + snipeSuccess = False + try: + id = Pokemons.id_for(pkm) + except: + self.sendMessage(chat_id=update.message.chat_id, parse_mode='Markdown', text="Invaild Pokemon") + return + #Set Telegram Snipe to true and let sniper do its work + TelegramSnipe.ENABLED = True + TelegramSnipe.ID = int(id) + TelegramSnipe.POKEMON_NAME = str(pkm) + TelegramSnipe.LATITUDE = float(lat) + TelegramSnipe.LONGITUDE = float(lng) + + outMsg = 'Catching pokemon: ' + TelegramSnipe.POKEMON_NAME + ' at Latitude: ' + str(TelegramSnipe.LATITUDE) + ' Longitude: ' + str(TelegramSnipe.LONGITUDE) + '\n' + self.sendMessage(chat_id=update.message.chat_id, parse_mode='Markdown', text="".join(outMsg)) + def send_evolved(self, update, num, order): evolved = self.chat_handler.get_evolved(num, order) outMsg = '' @@ -303,6 +328,7 @@ def send_start(self, update): "/pokestops - show last x pokestops visited", "/released - show top x released, sorted by CP, IV, or Date", "/vanished - show top x vanished, sorted by CP, IV, or Date", + "/snipe - to snipe a pokemon at location Latitude, Longitude", "/softbans - info about possible softbans" ) self.sendMessage(chat_id=update.message.chat_id, parse_mode='Markdown', @@ -432,6 +458,14 @@ def run(self): (cmd, num, order) = self.tokenize(update.message.text, 3) self.send_vanished(update, num, order) continue + if re.match(r'^/snipe ', update.message.text): + try: + (cmd, pkm, lat, lng) = self.tokenize(update.message.text, 4) + self.request_snipe(update, pkm, lat, lng) + except: + self.sendMessage(chat_id=update.message.chat_id, parse_mode='Markdown', + text="An Error has occured") + continue if re.match(r'^/softbans ', update.message.text): (cmd, num) = self.tokenize(update.message.text, 2) self.send_softbans(update, num)