From f19cf3f88e41ddc4a8ab269a345aa074ad436d02 Mon Sep 17 00:00:00 2001 From: Davis PoGo Date: Sat, 27 Jan 2018 23:39:04 -0800 Subject: [PATCH 1/4] Couple more fixes for 3.6 --- PokeAlarm/Events/WeatherEvent.py | 5 +++-- PokeAlarm/Filters/EggFilter.py | 4 ++-- PokeAlarm/Filters/RaidFilter.py | 4 ++-- PokeAlarm/Load.py | 3 ++- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/PokeAlarm/Events/WeatherEvent.py b/PokeAlarm/Events/WeatherEvent.py index 9711b9e8e..cab1fb88d 100644 --- a/PokeAlarm/Events/WeatherEvent.py +++ b/PokeAlarm/Events/WeatherEvent.py @@ -4,7 +4,7 @@ # Local Imports from PokeAlarm import Unknown from . import BaseEvent -from PokeAlarm.Utils import get_time_as_str +from PokeAlarm.Utils import get_time_as_str, get_weather_emoji class WeatherEvent(BaseEvent): @@ -40,7 +40,7 @@ def __init__(self, data): self.geofence = Unknown.REGULAR self.custom_dts = {} - def generate_dts(self, locale): + def generate_dts(self, locale, timezone, units): """ Return a dict with all the DTS for this event. """ time_changed = get_time_as_str(self.time_changed) dts = self.custom_dts.copy() @@ -61,6 +61,7 @@ def generate_dts(self, locale): # Weather info 'condition': self.condition, 'weather': locale.get_weather_name(self.condition), + 'weather_emoji': get_weather_emoji(self.condition), 'alert_severity': self.alert_severity, 'warn': self.warn, 'day': self.day diff --git a/PokeAlarm/Filters/EggFilter.py b/PokeAlarm/Filters/EggFilter.py index b4120599b..80bb947df 100644 --- a/PokeAlarm/Filters/EggFilter.py +++ b/PokeAlarm/Filters/EggFilter.py @@ -50,13 +50,13 @@ def __init__(self, name, data): self.gym_sponsor_index_contains = self.evaluate_attribute( event_attribute='gym_sponsor', eval_func=GymUtils.match_regex_dict, limit=BaseFilter.parse_as_set( - re.compile, 'gym_sponsor_index_contains', data)) + GymUtils.create_regex, 'gym_sponsor_index_contains', data)) # Gym park self.gym_park_contains = self.evaluate_attribute( # f.gp matches e.gp event_attribute='gym_park', eval_func=GymUtils.match_regex_dict, limit=BaseFilter.parse_as_set( - re.compile, 'gym_park_contains', data)) + GymUtils.create_regex, 'gym_park_contains', data)) # Team Info self.old_team = self.evaluate_attribute( # f.ctis contains m.cti diff --git a/PokeAlarm/Filters/RaidFilter.py b/PokeAlarm/Filters/RaidFilter.py index 114c35116..2538c1ee1 100644 --- a/PokeAlarm/Filters/RaidFilter.py +++ b/PokeAlarm/Filters/RaidFilter.py @@ -76,13 +76,13 @@ def __init__(self, name, data): self.gym_sponsor_index_contains = self.evaluate_attribute( event_attribute='gym_sponsor', eval_func=GymUtils.match_regex_dict, limit=BaseFilter.parse_as_set( - re.compile, 'gym_sponsor_index_contains', data)) + GymUtils.create_regex, 'gym_sponsor_index_contains', data)) # Gym park self.gym_park_contains = self.evaluate_attribute( event_attribute='gym_park', eval_func=GymUtils.match_regex_dict, limit=BaseFilter.parse_as_set( - re.compile, 'gym_park_contains', data)) + GymUtils.create_regex, 'gym_park_contains', data)) # Team Info self.old_team = self.evaluate_attribute( # f.ctis contains m.cti diff --git a/PokeAlarm/Load.py b/PokeAlarm/Load.py index e0f67d13a..838353ea2 100644 --- a/PokeAlarm/Load.py +++ b/PokeAlarm/Load.py @@ -47,7 +47,8 @@ def parse_rules_file(manager, filename): load_rules_section(manager.add_egg_rule, rules.pop('eggs', {})) log.debug("Parsing 'raids' section.") load_rules_section(manager.add_raid_rule, rules.pop('raids', {})) - + log.debug("Parsing 'weather' section.") + load_rules_section(manager.add_weather_rule, rules.pop('weather', {})) for key in rules: raise ValueError("Unknown Event type '{}'. Rules must be defined " "under the correct event type. See " From 5c952d49b03e24045463bf386c154abeddd01fa4 Mon Sep 17 00:00:00 2001 From: Davis PoGo Date: Sun, 28 Jan 2018 22:16:29 -0800 Subject: [PATCH 2/4] Enable Multi-Filtering with Multi-Geofence and dynamic webhook url --- PokeAlarm/Alarms/Discord/DiscordAlarm.py | 21 +- PokeAlarm/Events/EggEvent.py | 6 + PokeAlarm/Events/MonEvent.py | 16 +- PokeAlarm/Events/RaidEvent.py | 10 +- PokeAlarm/Events/WeatherEvent.py | 4 + PokeAlarm/LocationServices/GoogleMaps.py | 37 ++-- PokeAlarm/Manager.py | 245 ++++++++++++++++++----- PokeAlarm/Utils.py | 16 +- README.md | 44 ++++ alarms.json.example | 3 +- channel_id.json.example | 43 ++++ data/weather_boosts.json | 14 +- filters.json.example | 36 ++++ geofence.txt.example | 8 +- start_pokealarm.py | 16 +- tools/webhook_test.py | 36 +++- 16 files changed, 436 insertions(+), 119 deletions(-) create mode 100644 channel_id.json.example diff --git a/PokeAlarm/Alarms/Discord/DiscordAlarm.py b/PokeAlarm/Alarms/Discord/DiscordAlarm.py index 0ba343e3b..5243ebbb0 100644 --- a/PokeAlarm/Alarms/Discord/DiscordAlarm.py +++ b/PokeAlarm/Alarms/Discord/DiscordAlarm.py @@ -3,7 +3,7 @@ import requests # 3rd Party Imports - +import itertools # Local Imports from PokeAlarm.Alarms import Alarm from PokeAlarm.Utils import parse_boolean, get_static_map_url, \ @@ -92,8 +92,7 @@ class DiscordAlarm(Alarm): # Gather settings and create alarm def __init__(self, settings, max_attempts, static_map_key): # Required Parameters - self.__webhook_url = require_and_remove_key( - 'webhook_url', settings, "'Discord' type alarms.") + self.__webhook_url = "https://discordapp.com/api/webhooks/" self.__max_attempts = max_attempts # Optional Alarm Parameters @@ -103,7 +102,7 @@ def __init__(self, settings, max_attempts, static_map_key): settings.pop('disable_embed', "False")) self.__avatar_url = settings.pop('avatar_url', "") self.__map = settings.pop('map', {}) - self.__static_map_key = static_map_key + self.__static_map_key = itertools.cycle(static_map_key) # Set Alert Parameters self.__monsters = self.create_alert_settings( @@ -146,10 +145,10 @@ def startup_message(self): def create_alert_settings(self, settings, default, kind): if kind == 'weather': static_map = get_static_weather_map_url( - settings.pop('map', self.__map), self.__static_map_key) + settings.pop('map', self.__map), next(self.__static_map_key)) else: static_map = get_static_map_url( - settings.pop('map', self.__map), self.__static_map_key) + settings.pop('map', self.__map), next(self.__static_map_key)) alert = { 'webhook_url': settings.pop('webhook_url', self.__webhook_url), 'username': settings.pop('username', default['username']), @@ -185,7 +184,7 @@ def send_alert(self, alert, info): }] if alert['map'] is not None: if info.get('alert_type') == 'weather': - coords = { + map_info = { 'lat1': info['coords'][0][0], 'lng1': info['coords'][0][1], 'lat2': info['coords'][1][0], @@ -194,14 +193,16 @@ def send_alert(self, alert, info): 'lng3': info['coords'][2][1], 'lat4': info['coords'][3][0], 'lng4': info['coords'][3][1], + 'gkey': next(self.__static_map_key), } else: - coords = { + map_info = { 'lat': info['lat'], - 'lng': info['lng'] + 'lng': info['lng'], + 'gkey': next(self.__static_map_key), } payload['embeds'][0]['image'] = { - 'url': replace(alert['map'], coords) + 'url': replace(alert['map'], map_info) } args = { 'url': replace(alert['webhook_url'], info), diff --git a/PokeAlarm/Events/EggEvent.py b/PokeAlarm/Events/EggEvent.py index 43881e20d..a64ecfc7b 100644 --- a/PokeAlarm/Events/EggEvent.py +++ b/PokeAlarm/Events/EggEvent.py @@ -55,7 +55,9 @@ def __init__(self, data): self.name = self.gym_id self.geofence = Unknown.REGULAR + self.geofence_list = [] self.custom_dts = {} + self.channel_id = Unknown.REGULAR def generate_dts(self, locale, timezone, units): """ Return a dict with all the DTS for this event. """ @@ -87,6 +89,8 @@ def generate_dts(self, locale, timezone, units): 'gmaps': get_gmaps_link(self.lat, self.lng), 'applemaps': get_applemaps_link(self.lat, self.lng), 'geofence': self.geofence, + 'geofence_list': self.geofence_list, + 'channel_id': self.channel_id, 'weather_id': self.weather_id, 'weather': weather_name, 'weather_or_empty': Unknown.or_empty(weather_name), @@ -100,7 +104,9 @@ def generate_dts(self, locale, timezone, units): 'gym_description': self.gym_description, 'gym_image': self.gym_image, 'gym_sponsor': self.gym_sponsor, + 'gym_sponsor_phrase': ("\nSponsored Gym" if Unknown.or_empty(self.gym_sponsor) else ""), 'gym_park': self.gym_park, + 'gym_park_phrase': ("\n***Possible EX Raid Location (" + self.gym_park + ")***" if Unknown.or_empty(self.gym_park) else ""), 'team_id': self.current_team_id, 'team_name': locale.get_team_name(self.current_team_id), 'team_leader': locale.get_leader_name(self.current_team_id) diff --git a/PokeAlarm/Events/MonEvent.py b/PokeAlarm/Events/MonEvent.py index ebab3c090..e189c39cb 100644 --- a/PokeAlarm/Events/MonEvent.py +++ b/PokeAlarm/Events/MonEvent.py @@ -84,8 +84,8 @@ def __init__(self, data): self.gender = MonUtils.get_gender_sym( check_for_none(int, data.get('gender'), Unknown.TINY)) - self.height = check_for_none(float, data.get('height'), Unknown.SMALL) - self.weight = check_for_none(float, data.get('weight'), Unknown.SMALL) + self.height = check_for_none(float, data.get('height'), 0) + self.weight = check_for_none(float, data.get('weight'), 0) if Unknown.is_not(self.height, self.weight): self.size_id = get_pokemon_size( self.monster_id, self.height, self.weight) @@ -96,7 +96,9 @@ def __init__(self, data): # Correct this later self.name = self.monster_id self.geofence = Unknown.REGULAR + self.geofence_list = [] self.custom_dts = {} + self.channel_id = Unknown.REGULAR def generate_dts(self, locale, timezone, units): """ Return a dict with all the DTS for this event. """ @@ -138,6 +140,8 @@ def generate_dts(self, locale, timezone, units): 'gmaps': get_gmaps_link(self.lat, self.lng), 'applemaps': get_applemaps_link(self.lat, self.lng), 'geofence': self.geofence, + 'geofence_list': self.geofence_list, + 'channel_id': self.channel_id, # Weather 'weather_id': self.weather_id, @@ -154,6 +158,10 @@ def generate_dts(self, locale, timezone, units): 'boosted_or_empty': locale.get_boosted_text() if \ Unknown.is_not(self.boosted_weather_id) and self.boosted_weather_id != 0 else '', + 'boosted_weather_phrase_or_empty': ( + "\nBoosted by {} weather".format(boosted_weather_name) if \ + Unknown.is_not(self.boosted_weather_id) and + self.boosted_weather_id != 0 else ''), # Encounter Stats 'mon_lvl': self.mon_lvl, @@ -213,8 +221,8 @@ def generate_dts(self, locale, timezone, units): # Cosmetic 'gender': self.gender, - 'height': self.height, - 'weight': self.weight, + 'height': "{:.2f}".format(self.height), + 'weight': "{:.2f}".format(self.weight), 'size': locale.get_size_name(self.size_id), # Misc diff --git a/PokeAlarm/Events/RaidEvent.py b/PokeAlarm/Events/RaidEvent.py index 83c6ef64b..5659f1bc9 100644 --- a/PokeAlarm/Events/RaidEvent.py +++ b/PokeAlarm/Events/RaidEvent.py @@ -73,7 +73,9 @@ def __init__(self, data): self.name = self.gym_id self.geofence = Unknown.REGULAR + self.geofence_list = [] self.custom_dts = {} + self.channel_id = Unknown.REGULAR def generate_dts(self, locale, timezone, units): """ Return a dict with all the DTS for this event. """ @@ -131,11 +133,12 @@ def generate_dts(self, locale, timezone, units): 'gmaps': get_gmaps_link(self.lat, self.lng), 'applemaps': get_applemaps_link(self.lat, self.lng), 'geofence': self.geofence, + 'geofence_list': self.geofence_list, + 'channel_id': self.channel_id, 'weather_id': self.weather_id, 'weather': weather_name, 'weather_or_empty': Unknown.or_empty(weather_name), 'weather_emoji': get_weather_emoji(self.weather_id), - 'boosted_weather_id': boosted_weather_id, 'boosted_weather': boosted_weather_name, 'boosted_weather_or_empty': ( '' if boosted_weather_id == 0 @@ -143,6 +146,9 @@ def generate_dts(self, locale, timezone, units): 'boosted_weather_emoji': get_weather_emoji(boosted_weather_id), 'boosted_or_empty': locale.get_boosted_text() if boss_level == 25 else '', + 'boosted_weather_phrase_or_empty': ( + "\nBoosted by {} weather".format(boosted_weather_name) + if boss_level == 25 else ''), # Raid Info 'raid_lvl': self.raid_lvl, @@ -177,7 +183,9 @@ def generate_dts(self, locale, timezone, units): 'gym_description': self.gym_description, 'gym_image': self.gym_image, 'gym_sponsor': self.gym_sponsor, + 'gym_sponsor_phrase': ("\nSponsored Gym" if Unknown.or_empty(self.gym_sponsor) else ""), 'gym_park': self.gym_park, + 'gym_park_phrase': ("\n***Possible EX Raid Location (" + self.gym_park + ")***" if Unknown.or_empty(self.gym_park) else ""), 'team_id': self.current_team_id, 'team_name': locale.get_team_name(self.current_team_id), 'team_leader': locale.get_leader_name(self.current_team_id) diff --git a/PokeAlarm/Events/WeatherEvent.py b/PokeAlarm/Events/WeatherEvent.py index cab1fb88d..153ed16ac 100644 --- a/PokeAlarm/Events/WeatherEvent.py +++ b/PokeAlarm/Events/WeatherEvent.py @@ -38,7 +38,9 @@ def __init__(self, data): self.name = self.weather_cell_id self.geofence = Unknown.REGULAR + self.geofence_list = [] self.custom_dts = {} + self.channel_id = Unknown.REGULAR def generate_dts(self, locale, timezone, units): """ Return a dict with all the DTS for this event. """ @@ -55,8 +57,10 @@ def generate_dts(self, locale, timezone, units): # Location 'coords': self.coords, + 'channel_id': self.channel_id, 'geofence': self.geofence, + 'geofence_list': self.geofence_list, # Weather info 'condition': self.condition, diff --git a/PokeAlarm/LocationServices/GoogleMaps.py b/PokeAlarm/LocationServices/GoogleMaps.py index 8efc5eacf..0bf1a3531 100644 --- a/PokeAlarm/LocationServices/GoogleMaps.py +++ b/PokeAlarm/LocationServices/GoogleMaps.py @@ -3,6 +3,7 @@ import traceback # 3rd Party Imports import googlemaps +import itertools # Local Imports log = logging.getLogger('LocService') @@ -13,11 +14,10 @@ class GoogleMaps(object): # Initialize the APIs def __init__(self, api_key, locale, units): - self.__client = googlemaps.Client( - key=api_key, timeout=3, retry_timeout=5) self.__locale = locale # Language to use for Geocoding results self.__units = units # imperial or metric + self.__google_key = itertools.cycle(api_key) # For Reverse Location API self.__reverse_location = False @@ -46,8 +46,9 @@ def add_optional_arguments(self, origin, dest, data): # Returns an array in the format [ Lat, Lng ], or exit if an error occurs. def get_location_from_name(self, location_name): try: - result = self.__client.geocode( - location_name, language=self.__locale) + result = googlemaps.Client( + key = next(self.__google_key), timeout=3, retry_timeout=5).geocode( + location_name, language=self.__locale) # Get the first (most likely) result loc = result[0]['geometry']['location'] latitude, longitude = loc.get("lat"), loc.get("lng") @@ -80,8 +81,9 @@ def __get_reverse_location(self, location): 'state': 'unknown', 'country': 'country' } try: - result = self.__client.reverse_geocode( - location, language=self.__locale)[0] + result = googlemaps.Client( + key = next(self.__google_key), timeout=3, retry_timeout=5).reverse_geocode( + location, language=self.__locale)[0] loc = {} for item in result['address_components']: for category in item['types']: @@ -109,7 +111,7 @@ def __get_reverse_location(self, location): self.__reverse_location_history[key] = details # memoize except Exception as e: log.error("Encountered error while getting reverse " - + "location data ({}: {})".format(type(e).__name__, e)) + + "location data ({}: {}, api: {})".format(type(e).__name__, e, gkey)) log.debug("Stack trace: \n {}".format(traceback.format_exc())) # Return results, even if unable to complete return details @@ -129,9 +131,10 @@ def __get_walking_data(self, origin, dest): return self.__walk_data_history[key] data = {'walk_dist': "unknown", 'walk_time': "unknown"} try: - result = self.__client.distance_matrix( - origin, dest, mode='walking', - units=self.__units, language=self.__locale) + result = googlemaps.Client( + key = next(self.__google_key), timeout=3, retry_timeout=5).distance_matrix( + origin, dest, mode='walking', + units=self.__units, language=self.__locale) result = result.get('rows')[0].get('elements')[0] data['walk_dist'] = result.get( 'distance').get('text').encode('utf-8') @@ -159,9 +162,10 @@ def __get_biking_data(self, origin, dest): return self.__bike_data_history[key] data = {'bike_dist': "unknown", 'bike_time': "unknown"} try: - result = self.__client.distance_matrix( - origin, dest, mode='bicycling', - units=self.__units, language=self.__locale) + result = googlemaps.Client( + key = next(self.__google_key), timeout=3, retry_timeout=5).distance_matrix( + origin, dest, mode='bicycling', + units=self.__units, language=self.__locale) result = result.get('rows')[0].get('elements')[0] data['bike_dist'] = result.get( 'distance').get('text').encode('utf-8') @@ -189,9 +193,10 @@ def __get_driving_data(self, origin, dest): return self.__driving_data_history[key] data = {'drive_dist': "unknown", 'drive_time': "unknown"} try: - result = self.__client.distance_matrix( - origin, dest, mode='driving', - units=self.__units, language=self.__locale) + result = googlemaps.Client( + key = next(self.__google_key), timeout=3, retry_timeout=5).distance_matrix( + origin, dest, mode='driving', + units=self.__units, language=self.__locale) result = result.get('rows')[0].get('elements')[0] data['drive_dist'] = result.get( 'distance').get('text').encode('utf-8') diff --git a/PokeAlarm/Manager.py b/PokeAlarm/Manager.py index f7cb19269..444fc7881 100644 --- a/PokeAlarm/Manager.py +++ b/PokeAlarm/Manager.py @@ -12,6 +12,7 @@ import gevent from gevent.queue import Queue from gevent.event import Event +import itertools # Local Imports import Alarms @@ -32,7 +33,7 @@ class Manager(object): def __init__(self, name, google_key, locale, units, timezone, time_limit, max_attempts, location, quiet, cache_type, filter_file, - geofence_file, alarm_file, debug): + geofence_file, alarm_file, debug, channel_id_file): # Set the name of the Manager self.__name = str(name).lower() log.info("----------- Manager '{}' ".format(self.__name) @@ -40,15 +41,10 @@ def __init__(self, name, google_key, locale, units, timezone, time_limit, self.__debug = debug # Get the Google Maps API - self.__google_key = None - self.__loc_service = None - if str(google_key).lower() != 'none': - self.__google_key = google_key - self.__loc_service = location_service_factory( - "GoogleMaps", google_key, locale, units) - else: - log.warning("NO GOOGLE API KEY SET - Reverse Location and" - + " Distance Matrix DTS will NOT be detected.") + self.__google_key = google_key + + self.__loc_service = location_service_factory( + "GoogleMaps", self.__google_key, locale, units) self.__locale = Locale(locale) # Setup the language-specific stuff self.__units = units # type of unit used for distances @@ -83,6 +79,11 @@ def __init__(self, name, google_key, locale, units, timezone, time_limit, self.geofences = None if str(geofence_file).lower() != 'none': self.geofences = load_geofence_file(get_path(geofence_file)) + + # Load in the file to get discord API key from geofence/filter-set + self.channel_id = {} + self.load_channel_id_file(get_path(channel_id_file)) + # Create the alarms to send notifications out with self.__alarms = [] self.load_alarms_file(get_path(alarm_file), int(max_attempts)) @@ -385,6 +386,37 @@ def load_alarms_file(self, file_path, max_attempts): log.debug("Stack trace: \n {}".format(traceback.format_exc())) sys.exit(1) + def load_channel_id_file(self, file_path): + log.info("Loading API keys from the file at {}".format(file_path)) + try: + with open(file_path, 'r') as f: + self.channel_id = json.load(f) + if type(self.channel_id) is not dict: + log.critical("API key file must be a dict objects " + + "- { {...}, {...}, ... {...} }") + sys.exit(1) + log.info("API Key file found") + return # all done + except ValueError as e: + log.error("Encountered error while loading Alarms file: " + + "{}: {}".format(type(e).__name__, e)) + log.error( + "PokeAlarm has encountered a 'ValueError' while loading the " + + " API key file. This typically means your file isn't in the " + + "correct json format. Try loading your file contents into" + + " a json validator.") + except IOError as e: + log.error("Encountered error while loading API key: " + + "{}: {}".format(type(e).__name__, e)) + log.error("PokeAlarm was unable to find a api key file " + + "at {}. Please check that this file".format(file_path) + + " exists and PA has read permissions.") + except Exception as e: + log.error("Encountered error while loading api key: " + + "{}: {}".format(type(e).__name__, e)) + log.debug("Stack trace: \n {}".format(traceback.format_exc())) + sys.exit(1) + # Check for optional arguments and enable APIs as needed def set_optional_args(self, line): # Reverse Location @@ -587,6 +619,12 @@ def process_monster(self, mon): mon.direction = get_cardinal_dir( [mon.lat, mon.lng], self.__location) + # Checks to see which geofences contain the event + if not self.match_geofences(mon): + log.debug("{} monster was skipped because not in any geofences" + "".format(mon.name)) + return + # Check for Rules rules = self.__mon_rules if len(rules) == 0: # If no rules, default to all @@ -596,16 +634,23 @@ def process_monster(self, mon): for r_name, rule in rules.iteritems(): # For all rules for f_name in rule.filter_names: # Check Filters in Rules f = self.__mon_filters.get(f_name) - passed = f.check_event(mon) and self.check_geofences(f, mon) + passed = f.check_event(mon) if not passed: continue # go to next filter - mon.custom_dts = f.custom_dts - if self.__quiet is False: - log.info("{} monster notification" - " has been triggered in rule '{}'!" - "".format(mon.name, r_name)) - self._trigger_mon(mon, rule.alarm_names) - break # Next rule + for geofence_name in mon.geofence_list: + if not self.get_channel_id(mon, f_name, geofence_name): + log.debug("No API key set for {} monster" + " notification for geofence: {}," + " filter set: {}!" + "".format(mon.name, geofence_name, f_name)) + continue + mon.custom_dts = f.custom_dts + mon.geofence = mon.geofence_list[0] if geofence_name not in self.geofences.iterkeys() else geofence_name + if self.__quiet is False: + log.info("{} monster notification" + " has been triggered in rule '{}', for geofence: {}, filter set: {} channel: {}!" + "".format(mon.name, r_name, geofence_name, f_name, mon.channel_id)) + self._trigger_mon(mon, rule.alarm_names) def _trigger_mon(self, mon, alarms): # Generate the DTS for the event @@ -807,7 +852,6 @@ def process_egg(self, egg): # Assigned cached info info = self.__cache.get_gym_info(egg.gym_id) - egg.current_team_id = self.__cache.get_gym_team(egg.gym_id) egg.gym_name = info['name'] egg.gym_description = info['description'] egg.gym_image = info['url'] @@ -819,6 +863,12 @@ def process_egg(self, egg): egg.direction = get_cardinal_dir( [egg.lat, egg.lng], self.__location) + # Checks to see which geofences contain the event + if not self.match_geofences(egg): + log.debug("{} egg was skipped because not in any geofences" + "".format(egg.name)) + return + # Check for Rules rules = self.__egg_rules if len(rules) == 0: # If no rules, default to all @@ -828,21 +878,28 @@ def process_egg(self, egg): for r_name, rule in rules.iteritems(): # For all rules for f_name in rule.filter_names: # Check Filters in Rules f = self.__egg_filters.get(f_name) - passed = f.check_event(egg) and self.check_geofences(f, egg) + passed = f.check_event(egg) if not passed: continue # go to next filter - egg.custom_dts = f.custom_dts - if self.__quiet is False: - log.info("{} egg notification" - " has been triggered in rule '{}'!" - "".format(egg.name, r_name)) - self._trigger_egg(egg, rule.alarm_names) - break # Next rule + for geofence_name in egg.geofence_list: + if not self.get_channel_id(egg, f_name, geofence_name): + log.debug("No API key set for {} egg" + " notification for geofence: {}," + " filter set: {}!" + "".format(egg.name, geofence_name, f_name)) + continue + egg.custom_dts = f.custom_dts + egg.geofence = egg.geofence_list[0] if geofence_name not in self.geofences.iterkeys() else geofence_name + if self.__quiet is False: + log.info("{} egg notification" + " has been triggered in rule '{}', for geofence: {}, filter set: {} channel: {}!" + "".format(egg.name, r_name, geofence_name, f_name, egg.channel_id)) + self._trigger_egg(egg, rule.alarm_names) def _trigger_egg(self, egg, alarms): # Generate the DTS for the event dts = egg.generate_dts(self.__locale, self.__timezone, self.__units) - dts.update(self.__cache.get_gym_info(egg.gym_id)) # update gym info + #dts.update(self.__cache.get_gym_info(egg.gym_id)) # update gym info # Get reverse geocoding if self.__loc_service: self.__loc_service.add_optional_arguments( @@ -889,7 +946,6 @@ def process_raid(self, raid): # Assigned cached info info = self.__cache.get_gym_info(raid.gym_id) - raid.current_team_id = self.__cache.get_gym_team(raid.gym_id) raid.gym_name = info['name'] raid.gym_description = info['description'] raid.gym_image = info['url'] @@ -901,6 +957,12 @@ def process_raid(self, raid): raid.direction = get_cardinal_dir( [raid.lat, raid.lng], self.__location) + # Checks to see which geofences contain the event + if not self.match_geofences(raid): + log.debug("{} raid was skipped because not in any geofences" + "".format(raid.name)) + return + # Check for Rules rules = self.__raid_rules if len(rules) == 0: # If no rules, default to all @@ -910,21 +972,28 @@ def process_raid(self, raid): for r_name, rule in rules.iteritems(): # For all rules for f_name in rule.filter_names: # Check Filters in Rules f = self.__raid_filters.get(f_name) - passed = f.check_event(raid) and self.check_geofences(f, raid) + passed = f.check_event(raid) if not passed: continue # go to next filter - raid.custom_dts = f.custom_dts - if self.__quiet is False: - log.info("{} raid notification" - " has been triggered in rule '{}'!" - "".format(raid.name, r_name)) - self._trigger_raid(raid, rule.alarm_names) - break # Next rule + for geofence_name in raid.geofence_list: + if not self.get_channel_id(raid, f_name, geofence_name): + log.debug("No API key set for {} raid" + " notification for geofence: {}," + " filter set: {}!" + "".format(raid.name, geofence_name, f_name)) + continue + raid.custom_dts = f.custom_dts + raid.geofence = raid.geofence_list[0] if geofence_name not in self.geofences.iterkeys() else geofence_name + if self.__quiet is False: + log.info("{} raid notification" + " has been triggered in rule '{}', for geofence: {}, filter set: {} channel: {}!" + "".format(raid.name, r_name, geofence_name, f_name, raid.channel_id)) + self._trigger_raid(raid, rule.alarm_names) def _trigger_raid(self, raid, alarms): # Generate the DTS for the event dts = raid.generate_dts(self.__locale, self.__timezone, self.__units) - dts.update(self.__cache.get_gym_info(raid.gym_id)) # update gym info + #dts.update(self.__cache.get_gym_info(raid.gym_id)) # update gym info # Get reverse geocoding if self.__loc_service: self.__loc_service.add_optional_arguments( @@ -961,6 +1030,12 @@ def process_weather(self, weather): self.__cache.update_cell_weather( weather.weather_cell_id, weather.condition) + # Checks to see which geofences contain the event + if not self.match_weather_geofences(weather): + log.debug("{} weather was skipped because not in any geofences" + "".format(weather.name)) + return + # Check for Rules rules = self.__weather_rules if len(rules) == 0: # If no rules, default to all @@ -970,17 +1045,23 @@ def process_weather(self, weather): for r_name, rule in rules.iteritems(): # For all rules for f_name in rule.filter_names: # Check Filters in Rules f = self.__weather_filters.get(f_name) - passed = f.check_event(weather) and \ - self.check_weather_geofences(f, weather) + passed = f.check_event(weather) if not passed: continue # go to next filter - weather.custom_dts = f.custom_dts - if self.__quiet is False: - log.info("{} weather notification" - " has been triggered in rule '{}'!" - "".format(weather.weather_cell_id, r_name)) - self._trigger_weather(weather, weather.alarm_names) - break # Next rule + for geofence_name in weather.geofence_list: + if not self.get_channel_id(weather, f_name, geofence_name): + log.debug("No API key set for {} weather" + " notification for geofence: {}," + " filter set: {}!" + "".format(weather.name, geofence_name, f_name)) + continue + weather.custom_dts = f.custom_dts + weather.geofence = weather.geofence_list[0] if geofence_name not in self.geofences.iterkeys() else geofence_name + if self.__quiet is False: + log.info("{} weather notification" + " has been triggered in rule '{}', for geofence: {}, filter set: {} channel: {}!" + "".format(weather.name, r_name, geofence_name, f_name, weather.channel_id)) + self._trigger_weather(weather, rule.alarm_names) def _trigger_weather(self, weather, alarms): # Generate the DTS for the event @@ -1020,10 +1101,33 @@ def check_geofences(self, f, e): f.reject(e, "not in geofences") return False + # Check to see if a notification is within the given range + def match_geofences(self, e): + """ Returns true if the event passes the filter's geofences. """ + if self.geofences is None: # No geofences set (Improve here) + return False + for name in self.geofences.iterkeys(): + gf = self.geofences.get(name) + if not gf: # gf doesn't exist + log.error("Cannot check geofence %s: does not exist!", name) + elif gf.contains(e.lat, e.lng): # e in gf + gf_name = gf.get_name() + log.debug("{} is in geofence {}!".format( + e.name, gf_name)) + e.geofence_list.append(gf_name) # Set the geofence for dts + if "-" in gf_name and gf_name.split('-')[0] not in e.geofence_list: + e.geofence_list.append(gf_name.split('-')[0]) + else: # e not in gf + log.debug("%s not in %s.", e.name, name) + if not e.geofence_list: + return False + else: + e.geofence_list.append('All') + return True # Check to see if a weather notification s2 cell # overlaps with a given range (geofence) - def check_weather_geofences(self, f, weather): + def check_weather_geofences(self, f, e): """ Returns true if the event passes the filter's geofences. """ if self.geofences is None or f.geofences is None: # No geofences set return True @@ -1034,13 +1138,48 @@ def check_weather_geofences(self, f, weather): gf = self.geofences.get(name) if not gf: # gf doesn't exist log.error("Cannot check geofence %s: does not exist!", name) - elif gf.check_overlap(weather): # weather cell overlaps gf + elif gf.check_overlap(e): # weather cell overlaps gf log.debug("{} is in geofence {}!".format( - weather.weather_cell_id, gf.get_name())) - weather.geofence = name # Set the geofence for dts + e.weather_cell_id, gf.get_name())) + e.geofence = name # Set the geofence for dts return True else: # weather not in gf - log.debug("%s not in %s.", weather.weather_cell_id, name) - f.reject(weather, "not in geofences") + log.debug("%s not in %s.", e.weather_cell_id, name) + f.reject(e, "not in geofences") return False + + def match_weather_geofences(self, e): + """ Returns true if the event passes the filter's geofences. """ + if self.geofences is None: # No geofences set (Improve here) + return False + for name in self.geofences.iterkeys(): + gf = self.geofences.get(name) + if not gf: # gf doesn't exist + log.error("Cannot check geofence %s: does not exist!", name) + elif gf.check_overlap(e): # e in gf + gf_name = gf.get_name() + log.debug("{} is in geofence {}!".format( + e.name, gf_name)) + e.geofence_list.append(gf_name) # Set the geofence for dts + if "-" in gf_name and gf_name.split('-')[0] not in e.geofence_list: + e.geofence_list.append(gf_name.split('-')[0]) + else: # e not in gf + log.debug("%s not in %s.", e.name, name) + if not e.geofence_list: + return False + else: + e.geofence_list.append('All') + return True + + def get_channel_id(self, e, filter_name, geofence_name): + try: + api_filter_name = filter_name.split('-')[0] + e.channel_id = self.channel_id[geofence_name][api_filter_name] + return True + except KeyError: + log.debug("error in geofence: %s filter: %s.", geofence_name, api_filter_name) + return False + + + # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/PokeAlarm/Utils.py b/PokeAlarm/Utils.py index 72ea76785..056a88db1 100644 --- a/PokeAlarm/Utils.py +++ b/PokeAlarm/Utils.py @@ -382,11 +382,11 @@ def get_static_map_url(settings, api_key=None): # TODO: optimize formatting map_ = ('https://maps.googleapis.com/maps/api/staticmap?' + query_center + '&' + query_markers + '&' + - query_maptype + '&' + query_size + '&' + query_zoom) + query_maptype + '&' + query_size + '&' + query_zoom + '&key=') - if api_key is not None: - map_ += ('&key=%s' % api_key) - log.debug("API_KEY added to static map url.") + # if api_key is not None: + # map_ += ('&key=%s' % api_key) + # log.debug("API_KEY added to static map url.") return map_ @@ -408,11 +408,11 @@ def get_static_weather_map_url(settings, api_key=None): map_ = ('https://maps.googleapis.com/maps/api/staticmap?' + query_maptype + '&' + query_size + - '&' + query_zoom + '&' + query_path) + '&' + query_zoom + '&' + query_path + '&key=') - if api_key is not None: - map_ += ('&key=%s' % api_key) - log.debug("API_KEY added to static map url.") + # if api_key is not None: + # map_ += ('&key=%s' % api_key) + # log.debug("API_KEY added to static map url.") return map_ # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/README.md b/README.md index 8daa2dd17..71d0edfab 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,50 @@ ![Python 2.7](https://img.shields.io/badge/python-2.7-blue.svg) ![license](https://img.shields.io/github/license/PokeAlarm/PokeAlarm.svg) +### How to use the OneManager branch + +####Overview: +This PA modification allows you to run all your alerts with 1 manager. It accomplishes this by three main additions +1) All the filters get checked. For each filter that is matched, an alarm is triggered +2) All matching geofence(s) for each event can trigger an alarm. (you do not need a duplicated filter set for each geofence) +3) A new file, channel_id.json, is required. This file translates your geofence+filter combination into the channel/webhook portion of the discord webhook url + + +channel_id.json, filters.json, and geofence.txt are all required and must all be formatted appropriately to make alerts work. + +See examples files to see an example set up similar to the one I use. + +####geofence.txt + +1) Formatted exactly the same as always. +2) The area names (ie [Area1]) must match the Area names used in channel_id.json +3) Do not set a geofence for "All". If an event matches any geofence, an alarm is also created for the corresponding Filter/Discord webhook url pair in the "All" key of channel_id.json. +4) Use SubAreas if you have multiple smaller areas within a larger area. For example, you have a discord channel for all ultra rare spawns within a city and you also have multiple channels for rare spawns occuring in each of many different neighborhoods within that city. In your geofence file, do not specify a geofence for [Area2], instead only specify geofences for each SubArea in the format [Area2-SubArea1] + + +####channel_id.json + +- This file should look a lot like the organization of your discord server. See the example file for format. Some important points: +1) You can include an "All" key, if an event occurs within any geofence, it can trigger an alarm using the "All" set of Filter names +2) The first dictionary level are Area/Filter pairs. The Area name in this file must must match the Area names used in geofence.txt +3) The second dictionary level are Filter/Discord Webhook Url pairs. The Filter name must match the Filter names in filters.json. The Discord Webhook Url is the last portion of the webhook url (ie everything after "discordapp.com/api/webhooks/" ) for the channel you want to send the alarm to. + + +####filters.json + +1) No format changes from standard PA +2) Do not use '"geofences": [ "Area" ]'. All geofences supplied in geofences.txt will be checked for every event +3) The Filter name must match the Filter name specified in channel_id.json. (Except as described below) +4) In setting the Filter name the hyphen character (ie "-"), has a special use. If you have multiple filters you want to send to one discord channel you can use the hyphen to have different Filter names in filters.json that will match the same Filter name in channel_id.json. For example, in filters.json the Filter name "UltraRare-1" and "UltraRare-2" will both match the "UltraRare" key in channel_id.json. Thus, an event matching the conditions for either filter "UltraRare-1" or "UltraRare-2" will use the same Discord Webhook url. + +####alarms.json + +1) Do not include '"webhook_url":"YOUR_WEBHOOK_URL"' in alarms.json +2) Make sure your alarm format is robust enough to accomidate all filters. Only 1 alarm file is used + + + + PokeAlarm is a highly configurable application that filters and relays alerts about PokemonGo to your favorite online service, allowing you to be first to know of any rare spawns or raids. ### Patch Notes diff --git a/alarms.json.example b/alarms.json.example index 19ea1a15b..f362a6f6b 100644 --- a/alarms.json.example +++ b/alarms.json.example @@ -1,8 +1,7 @@ { "discord_alarm":{ "active":false, - "type":"discord", - "webhook_url":"YOUR_WEBHOOK_URL" + "type":"discord" }, "facebook_alarm":{ "active":false, diff --git a/channel_id.json.example b/channel_id.json.example new file mode 100644 index 000000000..4bd2ca5e0 --- /dev/null +++ b/channel_id.json.example @@ -0,0 +1,43 @@ +{ + "All":{ + "OIV": "XXXXXXXXXXXXXXXX/YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY", + "100IV: "XXXXXXXXXXXXXXXX/YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY", + "Rare": "XXXXXXXXXXXXXXXX/YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY", + "UltraRare": "XXXXXXXXXXXXXXXX/YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY", + "Candy": "XXXXXXXXXXXXXXXX/YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY", + "Event": "XXXXXXXXXXXXXXXX/YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY", + "Unown": "XXXXXXXXXXXXXXXX/YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY", + "SponsoredRaid: "XXXXXXXXXXXXXXXX/YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY" + }, + "Area1":{ + "OIV": "XXXXXXXXXXXXXXXX/YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY", + "100IV: "XXXXXXXXXXXXXXXX/YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY", + "Rare": "XXXXXXXXXXXXXXXX/YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY", + "Candy": "XXXXXXXXXXXXXXXX/YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY", + "Event": "XXXXXXXXXXXXXXXX/YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY", + "Raid5": "XXXXXXXXXXXXXXXX/YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY", + "Raid34": "XXXXXXXXXXXXXXXX/YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY" + }, + "Area2":{ + "100IV: "XXXXXXXXXXXXXXXX/YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY", + "Raid5": "XXXXXXXXXXXXXXXX/YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY" + }, + "Area2-SubArea1":{ + "OIV": "XXXXXXXXXXXXXXXX/YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY", + "100IV: "XXXXXXXXXXXXXXXX/YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY", + "Rare": "XXXXXXXXXXXXXXXX/YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY", + "Candy": "XXXXXXXXXXXXXXXX/YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY", + "Event": "XXXXXXXXXXXXXXXX/YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY", + "Raid5": "XXXXXXXXXXXXXXXX/YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY", + "Raid34": "XXXXXXXXXXXXXXXX/YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY" + }, + "Area2-SubArea2":{ + "OIV": "XXXXXXXXXXXXXXXX/YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY", + "100IV: "XXXXXXXXXXXXXXXX/YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY", + "Rare": "XXXXXXXXXXXXXXXX/YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY", + "Candy": "XXXXXXXXXXXXXXXX/YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY", + "Event": "XXXXXXXXXXXXXXXX/YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY", + "Raid5": "XXXXXXXXXXXXXXXX/YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY", + "Raid34": "XXXXXXXXXXXXXXXX/YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY" + } +} \ No newline at end of file diff --git a/data/weather_boosts.json b/data/weather_boosts.json index 367140d7a..2f39c9161 100644 --- a/data/weather_boosts.json +++ b/data/weather_boosts.json @@ -1,9 +1,9 @@ { - "1": [9, 11, 4], - "2": [10, 12, 6], - "3": [0, 5], - "4": [1, 16, 3], - "5": [15, 2, 13], - "6": [14, 8], - "7": [17, 7] + "1": [10, 12, 5], + "2": [11, 13, 7], + "3": [1, 6], + "4": [2, 17, 4], + "5": [16, 3, 14], + "6": [15, 9], + "7": [18, 8] } \ No newline at end of file diff --git a/filters.json.example b/filters.json.example index c162711df..b35ed9b16 100644 --- a/filters.json.example +++ b/filters.json.example @@ -18,6 +18,29 @@ "quick_moves":["Vine Whip","Tackle"], "charge_moves":["Sludge Bomb","Seed Bomb"] "weather": [ "Clear", 2 ], + }, + "Rare": { + + }, + "0IV": { + "min_iv": 0.0, "max_iv": 0.1 + }, + "100IV": { + "min_iv": 99.0, "max_iv": 100 + }, + "Unown": { + "monsters": [ 201 ] + }, + "Event-0": { + "monsters": [ 254, 257, 260, 281, 286, 287, 288, 289, 297, 308, 310, 317, 318, 320, 321, 326, 340, 342, 349, 350, 354, 356, 365 ] + }, + "Event-1+2": { + "monsters": [ 252, 253, 255, 256, 258, 259, 261, 262, 264, 266, 267, 268, 269, 270, 271, 272, 274, 275, 276, 277, 278, 279, 280, 282, 283, 284, 285, 290, 291, 292, 293, 294, 295, 296, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 309, 311, 312, 313, 314, 316, 319, 322, 323, 324, 325, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 341, 343, 344, 345, 346, 347, 348, 351, 352, 353, 355, 357, 358, 359, 360, 361, 362, 363, 364, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 112, 113, 135, 136, 229 ], + "min_iv": 90, "max_iv": 100 + }, + "Event-3": { + "monsters": [ 263, 265, 273, 315 ], + "min_iv": 99, "max_iv": 100 } } }, @@ -55,6 +78,10 @@ "min_egg_lvl": 0, "max_egg_lvl": 5 "gym_park_contains": [ "^(?!.*None)" ], "gym_sponsor_index_contains": ["^(?!.*0)"], + }, + "Raid5": { + "min_egg_lvl": 5, "max_egg_lvl": 5, + "is_missing_info": false } } }, @@ -75,6 +102,15 @@ "geofences": [ "Central Park" ], "custom_dts": { "key1": "value1", "key2": "value2" }, "is_missing_info": false + }, + "Raid5": { + "min_raid_lvl": 5, "max_raid_lvl": 5 + }, + "Raid34": { + "min_raid_lvl": 3, "max_raid_lvl": 4 + }, + "SponsoredRaid" : { + "gym_sponsor_index_contains": [ ".*" ] } } }, diff --git a/geofence.txt.example b/geofence.txt.example index 475b2008a..bfeef48e5 100644 --- a/geofence.txt.example +++ b/geofence.txt.example @@ -1,5 +1,9 @@ -[Central Park] +[Area1] 40.801206,-73.958520 40.767827,-73.982835 40.763798,-73.972808 -40.797343,-73.948385 \ No newline at end of file +40.797343,-73.948385 +[Area2-SubArea1] + +[Area2-SubArea2] + \ No newline at end of file diff --git a/start_pokealarm.py b/start_pokealarm.py index b109c2078..eeb24690b 100644 --- a/start_pokealarm.py +++ b/start_pokealarm.py @@ -140,7 +140,7 @@ def parse_settings(root_path): action='append', default=[], help='Names of Manager processes to start.') parser.add_argument( - '-k', '--key', type=parse_unicode, action='append', default=[None], + '-k', '--key', type=str, action='append', default=[], help='Specify a Google API Key to use.') parser.add_argument( '-f', '--filters', type=parse_unicode, action='append', @@ -185,6 +185,10 @@ def parse_settings(root_path): parser.add_argument( '-tz', '--timezone', type=str, action='append', default=[None], help='Timezone used for notifications. Ex: "America/Los_Angeles"') + parser.add_argument( + '-api', '--channel_id', type=parse_unicode, action='append', + default=['channel_id.json'], + help='Translate Filter set and Geofence to Discord API key. default: channel_id.json') args = parser.parse_args() @@ -201,10 +205,10 @@ def parse_settings(root_path): config['DEBUG'] = args.debug # Check to make sure that the same number of arguments are included - for arg in [args.key, args.filters, args.alarms, args.rules, + for arg in [args.filters, args.alarms, args.rules, args.geofences, args.location, args.locale, args.units, args.cache_type, args.timelimit, args.max_attempts, - args.timezone]: + args.timezone, args.channel_id]: if len(arg) > 1: # Remove defaults from the list arg.pop(0) size = len(arg) @@ -240,8 +244,7 @@ def parse_settings(root_path): config['UNITS'] = get_from_list(args.units, m_ct, args.units[0]) m = Manager( name=args.manager_name[m_ct], - google_key=get_from_list( - args.key, m_ct, args.key[0]), + google_key=args.key, locale=get_from_list(args.locale, m_ct, args.locale[0]), units=get_from_list(args.units, m_ct, args.units[0]), timezone=get_from_list(args.timezone, m_ct, args.timezone[0]), @@ -256,7 +259,8 @@ def parse_settings(root_path): geofence_file=get_from_list( args.geofences, m_ct, args.geofences[0]), alarm_file=get_from_list(args.alarms, m_ct, args.alarms[0]), - debug=config['DEBUG'] + debug=config['DEBUG'], + channel_id_file=get_from_list(args.channel_id, m_ct, args.channel_id[0]) ) parse_rules_file(m, get_from_list(args.rules, m_ct, args.rules[0])) if m.get_name() not in managers: diff --git a/tools/webhook_test.py b/tools/webhook_test.py index f91ffaa48..e7c0d021b 100644 --- a/tools/webhook_test.py +++ b/tools/webhook_test.py @@ -6,6 +6,8 @@ import os import portalocker import pickle +from random import randint + truthy = frozenset([ "yes", "Yes", "y", "Y", "true", "True", "TRUE", "YES", "1", "!0" @@ -16,7 +18,8 @@ "2": "pokestop", "3": "gym", "4": "egg", - "5": "raid" + "5": "raid", + "6": "weather" } teams = { @@ -70,8 +73,8 @@ def set_init(webhook_type): "pokemon_id": 149, "pokemon_level": 30, "player_level": 30, - "latitude": 37.7876146, - "longitude": -122.390624, + "latitude": 38.556814, + "longitude": -121.725527, "encounter_id": current_time, "cp_multiplier": 0.7317000031471252, "form": None, @@ -89,7 +92,7 @@ def set_init(webhook_type): "spawn_end": 3264, "verified": False, "weather": 0, - "boosted_weather": 0 + "boosted_weather": None, } } elif webhook_type == whtypes["2"]: @@ -130,8 +133,8 @@ def set_init(webhook_type): "park": None, "sponsor": 4, "level": 5, - "latitude": 37.7876146, - "longitude": -122.390624 + "latitude": 38.414232, + "longitude": -121.379004, } } elif webhook_type == whtypes["5"]: @@ -141,19 +144,32 @@ def set_init(webhook_type): "gym_id": 0, "name": "Test gym", "team": 1, - "park": "Test park", - "sponsor": 4, + "park": None, + "sponsor": 2, "weather": 5, "pokemon_id": 150, "cp": 12345, "move_1": 123, "move_2": 123, "level": 5, - "latitude": 37.7876146, - "longitude": -122.390624, + "latitude": 38.414232, + "longitude": -121.379004, "weather": 0 } } + elif webhook_type == whtypes["6"]: + payloadr = { + "type": "weather", + "message": { + "s2_cell_id": 0, + 'time_changed': current_time, + 'coords': [[38.25522067755094,-122.08374449567627],[38.179280284460866,-122.08374449567626],[38.20693488934502,-121.99280779590266],[38.282892940885574,-121.99280779590266]], + 'condition': randint(1, 6), + 'alert_severity': 0, + 'warn': 1, + 'day': 1, + } + } return payloadr From dd6ce043d081a05ec189820adc35261e99bca388 Mon Sep 17 00:00:00 2001 From: Davis PoGo Date: Thu, 8 Mar 2018 23:39:08 -0800 Subject: [PATCH 3/4] First GF only and some others --- PokeAlarm/LocationServices/GoogleMaps.py | 3 ++- PokeAlarm/Manager.py | 12 +++++------- data/base_stats.json | 6 +++--- start_pokealarm.py | 2 +- 4 files changed, 11 insertions(+), 12 deletions(-) diff --git a/PokeAlarm/LocationServices/GoogleMaps.py b/PokeAlarm/LocationServices/GoogleMaps.py index 0bf1a3531..212c9dead 100644 --- a/PokeAlarm/LocationServices/GoogleMaps.py +++ b/PokeAlarm/LocationServices/GoogleMaps.py @@ -81,8 +81,9 @@ def __get_reverse_location(self, location): 'state': 'unknown', 'country': 'country' } try: + gkey = next(self.__google_key) result = googlemaps.Client( - key = next(self.__google_key), timeout=3, retry_timeout=5).reverse_geocode( + key = gkey, timeout=3, retry_timeout=5).reverse_geocode( location, language=self.__locale)[0] loc = {} for item in result['address_components']: diff --git a/PokeAlarm/Manager.py b/PokeAlarm/Manager.py index 444fc7881..2ba4f1452 100644 --- a/PokeAlarm/Manager.py +++ b/PokeAlarm/Manager.py @@ -1115,15 +1115,13 @@ def match_geofences(self, e): log.debug("{} is in geofence {}!".format( e.name, gf_name)) e.geofence_list.append(gf_name) # Set the geofence for dts - if "-" in gf_name and gf_name.split('-')[0] not in e.geofence_list: - e.geofence_list.append(gf_name.split('-')[0]) + e.geofence_list.append('All') + if "-" in gf_name: + e.geofence_list.append(gf_name.split('-')[1]) + return True else: # e not in gf log.debug("%s not in %s.", e.name, name) - if not e.geofence_list: - return False - else: - e.geofence_list.append('All') - return True + return False # Check to see if a weather notification s2 cell # overlaps with a given range (geofence) diff --git a/data/base_stats.json b/data/base_stats.json index 2594649e5..f901da1a2 100644 --- a/data/base_stats.json +++ b/data/base_stats.json @@ -4010,9 +4010,9 @@ "height": 3.51 }, "384": { - "attack": 312, - "defense": 187, - "stamina": 210, + "attack": 284, + "defense": 170, + "stamina": 191, "type1": 16, "type2": 3, "legendary": true, diff --git a/start_pokealarm.py b/start_pokealarm.py index eeb24690b..6c87b7708 100644 --- a/start_pokealarm.py +++ b/start_pokealarm.py @@ -140,7 +140,7 @@ def parse_settings(root_path): action='append', default=[], help='Names of Manager processes to start.') parser.add_argument( - '-k', '--key', type=str, action='append', default=[], + '-k', '--key', type=str, action='append', default=[None], help='Specify a Google API Key to use.') parser.add_argument( '-f', '--filters', type=parse_unicode, action='append', From 35f277aaacebe73b083f1ffdc99481518a2f6983 Mon Sep 17 00:00:00 2001 From: Davis PoGo Date: Fri, 9 Mar 2018 22:42:10 -0800 Subject: [PATCH 4/4] Fix weather subarea-area order --- PokeAlarm/Manager.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/PokeAlarm/Manager.py b/PokeAlarm/Manager.py index 2ba4f1452..57b6b9f08 100644 --- a/PokeAlarm/Manager.py +++ b/PokeAlarm/Manager.py @@ -1154,20 +1154,18 @@ def match_weather_geofences(self, e): gf = self.geofences.get(name) if not gf: # gf doesn't exist log.error("Cannot check geofence %s: does not exist!", name) - elif gf.check_overlap(e): # e in gf + elif gf.contains(e.lat, e.lng): # e in gf gf_name = gf.get_name() log.debug("{} is in geofence {}!".format( e.name, gf_name)) e.geofence_list.append(gf_name) # Set the geofence for dts - if "-" in gf_name and gf_name.split('-')[0] not in e.geofence_list: - e.geofence_list.append(gf_name.split('-')[0]) + e.geofence_list.append('All') + if "-" in gf_name: + e.geofence_list.append(gf_name.split('-')[1]) + return True else: # e not in gf log.debug("%s not in %s.", e.name, name) - if not e.geofence_list: - return False - else: - e.geofence_list.append('All') - return True + return False def get_channel_id(self, e, filter_name, geofence_name): try: