class SlackAlarm(Alarm): _defaults = { 'pokemon': { 'username': "******", 'icon_url': get_image_url("monsters/<mon_id_3>_<form_id_3>.png"), 'title': "A wild <mon_name> has appeared!", 'url': "<gmaps>", 'body': "Available until <24h_time> (<time_left>)." }, 'pokestop': { 'username': "******", 'icon_url': get_image_url("stop/ready.png"), 'title': "Someone has placed a lure on a Pokestop!", 'url': "<gmaps>", 'body': "Lure will expire at <24h_time> (<time_left>)." }, 'gym': { 'username': "******", 'icon_url': get_image_url("gyms/<new_team_id>.png"), 'title': "A Team <old_team> gym has fallen!", 'url': "<gmaps>", 'body': "It is now controlled by <new_team>." }, 'egg': { 'username': "******", 'icon_url': get_image_url("eggs/<egg_lvl>.png"), 'title': "A level <egg_lvl> raid is incoming!", 'url': "<gmaps>", 'body': "The egg will hatch <24h_hatch_time> (<hatch_time_left>)." }, 'raid': { 'username': "******", 'icon_url': get_image_url("monsters/<mon_id_3>_000.png"), 'title': "Level <raid_lvl> raid is available against <mon_name>!", 'url': "<gmaps>", 'body': "The raid is available until <24h_raid_end> " "(<raid_time_left>)." } } # Gather settings and create alarm def __init__(self, settings, static_map_key): # Required Parameters self.__api_key = require_and_remove_key('api_key', settings, "'Slack' type alarms.") self.__default_channel = self.channel_format( require_and_remove_key('channel', settings, "'Slack' type alarms.")) self.__client = None self.__channels = {} # Optional Alarm Parameters self.__startup_message = parse_boolean( settings.pop('startup_message', "True")) self.__map = settings.pop('map', {}) self.__static_map_key = static_map_key # Optional Alert Parameters self.__pokemon = self.create_alert_settings( settings.pop('pokemon', {}), self._defaults['pokemon']) self.__pokestop = self.create_alert_settings( settings.pop('pokestop', {}), self._defaults['pokestop']) self.__gym = self.create_alert_settings(settings.pop('gym', {}), self._defaults['gym']) self.__egg = self.create_alert_settings(settings.pop('egg', {}), self._defaults['egg']) self.__raid = self.create_alert_settings(settings.pop('raid', {}), self._defaults['raid']) # Warn user about leftover parameters reject_leftover_parameters(settings, "'Alarm level in Slack alarm.") log.info("Slack Alarm has been created!") # Establish connection with Slack def connect(self): self.__client = Slacker(self.__api_key) self.update_channels() # Send a message letting the channel know that this alarm started def startup_message(self): if self.__startup_message: self.send_message(self.__default_channel, username="******", text="PokeAlarm activated!") log.info("Startup message sent!") # Set the appropriate settings for each alert def create_alert_settings(self, settings, default): alert = { 'channel': settings.pop('channel', self.__default_channel), 'username': settings.pop('username', default['username']), 'icon_url': settings.pop('icon_url', default['icon_url']), 'title': settings.pop('title', default['title']), 'url': settings.pop('url', default['url']), 'body': settings.pop('body', default['body']), 'map': get_static_map_url(settings.pop('map', self.__map), self.__static_map_key) } reject_leftover_parameters(settings, "'Alert level in Slack alarm.") return alert # Send Alert to Slack def send_alert(self, alert, info): coords = {'lat': info['lat'], 'lng': info['lng']} attachments = [{ 'fallback': 'Map_Preview', 'image_url': replace(alert['map'], coords) }] if alert['map'] is not None else None self.send_message(channel=replace(alert['channel'], info), username=replace(alert['username'], info), text='<{}|{}> - {}'.format( replace(alert['url'], info), replace(alert['title'], info), replace(alert['body'], info)), icon_url=replace(alert['icon_url'], info), attachments=attachments) # Trigger an alert based on Pokemon info def pokemon_alert(self, pokemon_info): self.send_alert(self.__pokemon, pokemon_info) # Trigger an alert based on Pokestop info def pokestop_alert(self, pokestop_info): self.send_alert(self.__pokestop, pokestop_info) # Trigger an alert based on Pokestop info def gym_alert(self, gym_info): self.send_alert(self.__gym, gym_info) # Trigger an alert when a raid egg has spawned (UPCOMING raid event) def raid_egg_alert(self, raid_info): self.send_alert(self.__egg, raid_info) # Trigger an alert based on Gym info def raid_alert(self, raid_info): self.send_alert(self.__raid, raid_info) # Get a list of channels from Slack to help def update_channels(self): self.__channels = {} response = self.__client.channels.list(True).body for channel in response['channels']: self.__channels[channel['name']] = channel['id'] response = self.__client.groups.list().body for channel in response['groups']: self.__channels[channel['name']] = channel['id'] log.debug("Detected the following Slack channnels: {}".format( self.__channels)) # Checks for valid channel, otherwise defaults to general def get_channel(self, name): channel = SlackAlarm.channel_format(name) if channel not in self.__channels: log.error("Detected no channel with the name '{}'." + " Trying the default channel '{}' instead.".format( channel, self.__default_channel)) return self.__default_channel return channel # Send a message to Slack def send_message(self, channel, username, text, icon_url=None, attachments=None): args = { "channel": self.get_channel(channel), "username": username, "text": text } if icon_url is not None: args['icon_url'] = icon_url if attachments is not None: args['attachments'] = attachments try_sending(log, self.connect, "Slack", self.__client.chat.post_message, args) # Returns a string s that is in proper channel format @staticmethod def channel_format(name): if name[0] == '#': # Remove # if added name = name[1:] name = name.replace(u"\u2642", "m").replace(u"\u2640", "f").lower() return re.sub("[^_a-z0-9-]+", "", name)
class Cache(object): """ Basic object for caching information. This object caches and manages information in Memory. Information will be lost between run times if save has not been implemented correctly. """ default_image_url = get_image_url("regular/gyms/0.png"), def __init__(self, mgr): """ Initializes a new cache object for storing data between events. """ self._log = mgr.get_child_logger("cache") self._mon_hist = {} self._stop_hist = {} self._egg_hist = {} self._raid_hist = {} self._quest_hist = {} self._grunt_hist = {} self._gym_team = {} self._gym_name = {} self._gym_desc = {} self._gym_image = {} self._cell_weather_id = {} self._severity_id = {} self._day_or_night_id = {} self._quest_reward = {} self._quest_task = {} self._quest_last_modified = {} def monster_expiration(self, mon_id, expiration=None): """ Update and return the datetime that a monster expires.""" if expiration is not None: self._mon_hist[mon_id] = expiration return self._mon_hist.get(mon_id) def stop_expiration(self, stop_id, expiration=None): """ Update and return the datetime that a stop expires.""" if expiration is not None: self._stop_hist[stop_id] = expiration return self._stop_hist.get(stop_id) def egg_expiration(self, egg_id, expiration=None): """ Update and return the datetime that an egg expires.""" if expiration is not None: self._egg_hist[egg_id] = expiration return self._egg_hist.get(egg_id) def raid_expiration(self, raid_id, expiration=None): """ Update and return the datetime that a raid expires.""" if expiration is not None: self._raid_hist[raid_id] = expiration return self._raid_hist.get(raid_id) def quest_expiration(self, stop_id, last_modified=None): """ Update and return the datetime that the stop last had a quest.""" if last_modified is not None: self._quest_hist[stop_id] = last_modified return self._quest_hist.get(stop_id) def grunt_expiration(self, stop_id, expiration=None): """ Update and return the datetime that a stop expires.""" if expiration is not None: self._grunt_hist[stop_id] = expiration return self._grunt_hist.get(stop_id) def gym_team(self, gym_id, team_id=Unknown.TINY): """ Update and return the team_id of a gym. """ if Unknown.is_not(team_id): self._gym_team[gym_id] = team_id return self._gym_team.get(gym_id, Unknown.TINY) def gym_name(self, gym_id, gym_name=Unknown.REGULAR): """ Update and return the gym_name for a gym. """ if Unknown.is_not(gym_name): self._gym_name[gym_id] = gym_name return self._gym_name.get(gym_id, Unknown.REGULAR) def gym_desc(self, gym_id, gym_desc=Unknown.REGULAR): """ Update and return the gym_desc for a gym. """ if Unknown.is_not(gym_desc): self._gym_desc[gym_id] = gym_desc return self._gym_desc.get(gym_id, Unknown.REGULAR) def gym_image(self, gym_id, gym_image=Unknown.REGULAR): """ Update and return the gym_image for a gym. """ if Unknown.is_not(gym_image): self._gym_image[gym_id] = gym_image return self._gym_image.get(gym_id, get_image_url('icons/gym_0.png')) def cell_weather_id(self, s2_cell_id, cell_weather_id=Unknown.REGULAR): """ Update and return weather_id for a cell """ if Unknown.is_not(cell_weather_id): self._cell_weather_id[s2_cell_id] = cell_weather_id return self._cell_weather_id.get(s2_cell_id, Unknown.REGULAR) def severity_id(self, s2_cell_id, severity_id=Unknown.REGULAR): """ Update and return severity_id for a cell """ if Unknown.is_not(severity_id): self._severity_id[s2_cell_id] = severity_id return self._severity_id.get(s2_cell_id, Unknown.REGULAR) def day_or_night_id(self, s2_cell_id, day_or_night_id=Unknown.REGULAR): """ Update and return day_or_night_id for a cell """ if Unknown.is_not(day_or_night_id): self._day_or_night_id[s2_cell_id] = day_or_night_id return self._day_or_night_id.get(s2_cell_id, Unknown.REGULAR) def quest_reward(self, stop_id, reward=None, task=None, last_modified=None): """ Update and return the reward and task for a quest.""" if Unknown.is_not(reward): self._quest_reward[stop_id] = reward if Unknown.is_not(task): self._quest_task[stop_id] = task if Unknown.is_not(last_modified): self._quest_last_modified[stop_id] = last_modified return self._quest_reward.get(stop_id, Unknown.REGULAR), \ self._quest_task.get(stop_id, Unknown.REGULAR), \ self._quest_last_modified.get(stop_id, Unknown.REGULAR) def clean_and_save(self): """ Cleans the cache and saves the contents if capable. """ self._clean_hist() self._save() def _save(self): """ Export the data to a more permanent location. """ pass # Mem cache isn't backed up. def _clean_hist(self): """ Clean expired objects to free up memory. """ for hist in (self._mon_hist, self._stop_hist, self._egg_hist, self._raid_hist, self._quest_hist, self._grunt_hist): old = [] now = datetime.utcnow() for key, expiration in hist.iteritems(): if expiration < now: # Track expired items old.append(key) for key in old: # Remove expired events del hist[key] self._log.debug("Cleared %s items from cache.", len(old))
def gym_image(self, gym_id, gym_image=Unknown.REGULAR): """ Update and return the gym_image for a gym. """ if Unknown.is_not(gym_image): self._gym_image[gym_id] = gym_image return self._gym_image.get(gym_id, get_image_url('icons/gym_0.png'))
class TelegramAlarm(Alarm): Alert = namedtuple("Alert", [ 'bot_token', 'chat_id', 'sticker', 'sticker_url', 'sticker_notify', 'message', 'message_notify', 'venue', 'venue_notify', 'map', 'map_notify', 'max_attempts', 'web_preview' ]) _defaults = { # No touchy!!! Edit alarms.json! 'monsters': { 'message': "*A wild <mon_name> has appeared!*\n" "Available until <24h_time> (<time_left>).", 'sticker_url': get_image_url("telegram/monsters/<mon_id_3>_<form_id_3>.webp") }, 'stops': { 'message': "*Someone has placed a lure on a Pokestop!*\n" "Lure will expire at <24h_time> (<time_left>).", 'sticker_url': get_image_url("telegram/stop/ready.webp") }, 'gyms': { 'message': "*A Team <old_team> gym has fallen!*\n" "It is now controlled by <new_team>.", 'sticker_url': get_image_url("telegram/gyms/<new_team_id>.webp"), }, 'eggs': { 'message': "*A level <egg_lvl> raid is incoming!*\n" "The egg will hatch <24h_hatch_time> " "(<hatch_time_left>).", 'sticker_url': get_image_url("telegram/eggs/<egg_lvl>.webp") }, 'raids': { 'message': "*A raid is available against <mon_name>!*\n" "The raid is available until <24h_raid_end> " "(<raid_time_left>).", 'sticker_url': get_image_url("telegram/monsters/<mon_id_3>_000.webp") } } # Gather settings and create alarm def __init__(self, settings): # Required Parameters self._bot_token = require_and_remove_key('bot_token', settings, "'Telegram' type alarms.") self._chat_id = require_and_remove_key('chat_id', settings, "'Telegram' type alarms.") self._startup_message = self.pop_type(settings, 'startup_message', utils.parse_bool, True) # Optional Alert Parameters alert_defaults = { 'bot_token': self._bot_token, 'chat_id': self._chat_id, 'sticker': self.pop_type(settings, 'sticker', utils.parse_bool, True), 'sticker_notify': self.pop_type(settings, 'sticker_notify', utils.parse_bool, False), 'message_notify': self.pop_type(settings, 'message_notify', utils.parse_bool, True), 'venue': self.pop_type(settings, 'venue', utils.parse_bool, False), 'venue_notify': self.pop_type(settings, 'venue_notify', utils.parse_bool, True), 'map': self.pop_type(settings, 'map', utils.parse_bool, True), 'map_notify': self.pop_type(settings, 'map_notify', utils.parse_bool, False), 'max_attempts': self.pop_type(settings, 'max_attempts', int, 3), 'web_preview': self.pop_type(settings, 'web_preview', utils.parse_bool, False) } # Alert Settings self._mon_alert = self.create_alert_settings('monsters', settings, alert_defaults) self._stop_alert = self.create_alert_settings('stops', settings, alert_defaults) self._gym_alert = self.create_alert_settings('gyms', settings, alert_defaults) self._egg_alert = self.create_alert_settings('eggs', settings, alert_defaults) self._raid_alert = self.create_alert_settings('raids', settings, alert_defaults) # Reject leftover parameters for key in settings: raise ValueError("'{}' is not a recognized parameter for the Alarm" " level in a Telegram Alarm".format(key)) log.info("Telegram Alarm has been created!") # (Re)establishes Telegram connection def connect(self): pass # Set the appropriate settings for each alert def create_alert_settings(self, kind, settings, alert_defaults): default = TelegramAlarm._defaults[kind] default.update(alert_defaults) settings = Alarm.pop_type(settings, kind, dict, {}) alert = TelegramAlarm.Alert( bot_token=Alarm.pop_type(settings, 'bot_token', unicode, default['bot_token']), chat_id=Alarm.pop_type(settings, 'chat_id', unicode, default['chat_id']), sticker=Alarm.pop_type(settings, 'sticker', utils.parse_bool, default['sticker']), sticker_url=Alarm.pop_type(settings, 'sticker_url', unicode, default['sticker_url']), sticker_notify=Alarm.pop_type(settings, 'sticker_notify', utils.parse_bool, default['sticker_notify']), message=Alarm.pop_type(settings, 'message', unicode, default['message']), message_notify=Alarm.pop_type(settings, 'message_notify', utils.parse_bool, default['message_notify']), venue=Alarm.pop_type(settings, 'venue', utils.parse_bool, default['venue']), venue_notify=Alarm.pop_type(settings, 'venue_notify', utils.parse_bool, default['venue_notify']), map=Alarm.pop_type(settings, 'map', utils.parse_bool, default['map']), map_notify=Alarm.pop_type(settings, 'map_notify', utils.parse_bool, default['map_notify']), max_attempts=Alarm.pop_type(settings, 'max_attempts', int, default['max_attempts']), web_preview=Alarm.pop_type(settings, 'web_preview', utils.parse_bool, default['web_preview'])) # Reject leftover parameters for key in settings: raise ValueError("'{}' is not a recognized parameter for the Alert" " level in a Telegram Alarm".format(key)) return alert # Sends a start up message on Telegram def startup_message(self): if self._startup_message: self.send_message(self._bot_token, self._chat_id, "PokeAlarm activated!") log.info("Startup message sent!") # Generic Telegram Alert def generic_alert(self, alert, dts): bot_token = replace(alert.bot_token, dts) chat_id = replace(alert.chat_id, dts) message = replace(alert.message, dts) lat, lng = dts['lat'], dts['lng'] max_attempts = alert.max_attempts sticker_url = replace(alert.sticker_url, dts) log.debug(sticker_url) # Send Sticker if alert.sticker and sticker_url is not None: self.send_sticker(bot_token, chat_id, sticker_url, max_attempts) # Send Venue if alert.venue: self.send_venue(bot_token, chat_id, lat, lng, message, max_attempts) return # Don't send message or map # Send Message self.send_message(bot_token, chat_id, replace(message, dts), web_preview=alert.web_preview) # Send Map if alert.map: self.send_location(bot_token, chat_id, lat, lng, max_attempts) # Trigger an alert based on Pokemon info def pokemon_alert(self, mon_dts): self.generic_alert(self._mon_alert, mon_dts) # Trigger an alert based on Pokestop info def pokestop_alert(self, stop_dts): self.generic_alert(self._stop_alert, stop_dts) # Trigger an alert based on Pokestop info def gym_alert(self, gym_dts): self.generic_alert(self._gym_alert, gym_dts) # Trigger an alert when a raid egg has spawned (UPCOMING raid event) def raid_egg_alert(self, egg_dts): self.generic_alert(self._egg_alert, egg_dts) # Trigger an alert based on Raid info def raid_alert(self, raid_dts): self.generic_alert(self._raid_alert, raid_dts) def send_sticker(self, token, chat_id, sticker_url, max_attempts=3, notify=False): args = { 'url': "https://api.telegram.org/bot{}/sendSticker".format(token), 'payload': { 'chat_id': chat_id, 'sticker': sticker_url, 'disable_notification': not notify } } try_sending(log, self.connect, "Telegram (STKR)", self.send_webhook, args, max_attempts) def send_message(self, token, chat_id, message, max_attempts=3, notify=True, web_preview=False): args = { 'url': "https://api.telegram.org/bot{}/sendMessage".format(token), 'payload': { 'chat_id': chat_id, 'text': message, 'parse_mode': 'Markdown', 'disable_web_page_preview': not web_preview, 'disable_notification': not notify } } try_sending(log, self.connect, "Telegram (MSG)", self.send_webhook, args, max_attempts) def send_location(self, token, chat_id, lat, lng, max_attempts=3, notify=False): args = { 'url': "https://api.telegram.org/bot{}/sendLocation".format(token), 'payload': { 'chat_id': chat_id, 'latitude': lat, 'longitude': lng, 'disable_notification': not notify } } try_sending(log, self.connect, "Telegram (LOC)", self.send_webhook, args, max_attempts) def send_venue(self, token, chat_id, lat, lng, message, max_attempts): msg = message.split('\n', 1) args = { 'url': "https://api.telegram.org/bot{}/sendVenue".format(token), 'payload': { 'chat_id': chat_id, 'latitude': lat, 'title': msg[0], 'address': msg[1] if len(msg) > 1 else '', 'longitude': lng, 'disable_notification': False } } try_sending(log, self.connect, "Telegram (VEN)", self.send_webhook, args, max_attempts) # Send a payload to the webhook url def send_webhook(self, url, payload): log.debug(url) log.debug(payload) resp = requests.post(url, json=payload, timeout=30) if resp.ok is True: log.debug("Notification successful (returned {})".format( resp.status_code)) else: log.debug("Telegram response was {}".format(resp.content)) raise requests.exceptions.RequestException( "Response received {}, webhook not accepted.".format( resp.status_code))
class DiscordAlarm(Alarm): _defaults = { 'monsters': { 'username': "******", 'content': "", 'icon_url': get_image_url("regular/monsters/<mon_id_3>_<form_id_3>.png"), 'avatar_url': get_image_url("regular/monsters/<mon_id_3>_<form_id_3>.png"), 'title': "A wild <mon_name> has appeared!", 'url': "<gmaps>", 'body': "Available until <24h_time> (<time_left>)." }, 'stops': { 'username': "******", 'content': "", 'icon_url': get_image_url("regular/stop/ready.png"), 'avatar_url': get_image_url("regular/stop/ready.png"), 'title': "Someone has placed a lure on a Pokestop!", 'url': "<gmaps>", 'body': "Lure will expire at <24h_time> (<time_left>)." }, 'gyms': { 'username': "******", 'content': "", 'icon_url': get_image_url("regular/gyms/<new_team_id>.png"), 'avatar_url': get_image_url("regular/gyms/<new_team_id>.png"), 'title': "A Team <old_team> gym has fallen!", 'url': "<gmaps>", 'body': "It is now controlled by <new_team>." }, 'eggs': { 'username': "******", 'content': "", 'icon_url': get_image_url("regular/eggs/<egg_lvl>.png"), 'avatar_url': get_image_url("regular/eggs/<egg_lvl>.png"), 'title': "Raid is incoming!", 'url': "<gmaps>", 'body': "A level <egg_lvl> raid will hatch at " "<24h_hatch_time> (<hatch_time_left>)." }, 'raids': { 'username': "******", 'content': "", 'icon_url': get_image_url("regular/monsters/<mon_id_3>_000.png"), 'avatar_url': get_image_url("regular/monsters/<mon_id_3>_000.png"), 'title': "Level <raid_lvl> raid is available against <mon_name>!", 'url': "<gmaps>", 'body': "The raid is available until " "<24h_raid_end> (<raid_time_left>)." } } # 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.__max_attempts = max_attempts # Optional Alarm Parameters self.__startup_message = parse_boolean( settings.pop('startup_message', "True")) self.__disable_embed = parse_boolean( settings.pop('disable_embed', "False")) self.__avatar_url = settings.pop('avatar_url', "") self.__map = settings.pop('map', {}) self.__static_map_key = static_map_key # Set Alert Parameters self.__monsters = self.create_alert_settings( settings.pop('monsters', {}), self._defaults['monsters']) self.__stops = self.create_alert_settings(settings.pop('stops', {}), self._defaults['stops']) self.__gyms = self.create_alert_settings(settings.pop('gyms', {}), self._defaults['gyms']) self.__eggs = self.create_alert_settings(settings.pop('eggs', {}), self._defaults['eggs']) self.__raids = self.create_alert_settings(settings.pop('raids', {}), self._defaults['raids']) # Warn user about leftover parameters reject_leftover_parameters(settings, "'Alarm level in Discord alarm.") log.info("Discord Alarm has been created!") # (Re)connect with Discord def connect(self): pass # Send a message letting the channel know that this alarm has started def startup_message(self): if self.__startup_message: args = { 'url': self.__webhook_url, 'payload': { 'username': '******', 'content': 'PokeAlarm activated!' } } try_sending(log, self.connect, "Discord", self.send_webhook, args, self.__max_attempts) log.info("Startup message sent!") # Set the appropriate settings for each alert def create_alert_settings(self, settings, default): alert = { 'webhook_url': settings.pop('webhook_url', self.__webhook_url), 'username': settings.pop('username', default['username']), 'avatar_url': settings.pop('avatar_url', default['avatar_url']), 'disable_embed': parse_boolean(settings.pop('disable_embed', self.__disable_embed)), 'content': settings.pop('content', default['content']), 'icon_url': settings.pop('icon_url', default['icon_url']), 'title': settings.pop('title', default['title']), 'url': settings.pop('url', default['url']), 'body': settings.pop('body', default['body']), 'map': get_static_map_url(settings.pop('map', self.__map), self.__static_map_key) } reject_leftover_parameters(settings, "'Alert level in Discord alarm.") return alert # Send Alert to Discord def send_alert(self, alert, info): log.debug("Attempting to send notification to Discord.") payload = { # Usernames are limited to 32 characters 'username': replace(alert['username'], info)[:32], 'content': replace(alert['content'], info), 'avatar_url': replace(alert['avatar_url'], info), } if alert['disable_embed'] is False: payload['embeds'] = [{ 'title': replace(alert['title'], info), 'url': replace(alert['url'], info), 'description': replace(alert['body'], info), 'thumbnail': { 'url': replace(alert['icon_url'], info) } }] if alert['map'] is not None: coords = {'lat': info['lat'], 'lng': info['lng']} payload['embeds'][0]['image'] = { 'url': replace(alert['map'], coords) } args = {'url': replace(alert['webhook_url'], info), 'payload': payload} try_sending(log, self.connect, "Discord", self.send_webhook, args, self.__max_attempts) # Trigger an alert based on Pokemon info def pokemon_alert(self, pokemon_info): log.debug("Pokemon notification triggered.") self.send_alert(self.__monsters, pokemon_info) # Trigger an alert based on Pokestop info def pokestop_alert(self, pokestop_info): log.debug("Pokestop notification triggered.") self.send_alert(self.__stops, pokestop_info) # Trigger an alert based on Pokestop info def gym_alert(self, gym_info): log.debug("Gym notification triggered.") self.send_alert(self.__gyms, gym_info) # Trigger an alert when a raid egg has spawned (UPCOMING raid event) def raid_egg_alert(self, raid_info): self.send_alert(self.__eggs, raid_info) def raid_alert(self, raid_info): self.send_alert(self.__raids, raid_info) # Send a payload to the webhook url def send_webhook(self, url, payload): log.debug(payload) resp = requests.post(url, json=payload, timeout=5) if resp.ok is True: log.debug("Notification successful (returned {})".format( resp.status_code)) else: log.debug("Discord response was {}".format(resp.content)) raise requests.exceptions.RequestException( "Response received {}, webhook not accepted.".format( resp.status_code))
class Cache(object): """ Basic object for caching information. This object caches and manages information in Memory. Information will be lost between run times if save has not been implemented correctly. """ default_image_url = get_image_url("regular/gyms/0.png"), def __init__(self): """ Initializes a new cache object for storing data between events. """ self._mon_hist = {} self._stop_hist = {} self._egg_hist = {} self._raid_hist = {} self._gym_team = {} self._gym_name = {} self._gym_desc = {} self._gym_image = {} def monster_expiration(self, mon_id, expiration=None): """ Update and return the datetime that a monster expires.""" if expiration is not None: self._mon_hist[mon_id] = expiration return self._mon_hist.get(mon_id) def stop_expiration(self, stop_id, expiration=None): """ Update and return the datetime that a stop expires.""" if expiration is not None: self._stop_hist[stop_id] = expiration return self._stop_hist.get(stop_id) def egg_expiration(self, egg_id, expiration=None): """ Update and return the datetime that an egg expires.""" if expiration is not None: self._egg_hist[egg_id] = expiration return self._egg_hist.get(egg_id) def raid_expiration(self, raid_id, expiration=None): """ Update and return the datetime that a raid expires.""" if expiration is not None: self._raid_hist[raid_id] = expiration return self._raid_hist.get(raid_id) def gym_team(self, gym_id, team_id=Unknown.TINY): """ Update and return the team_id of a gym. """ if Unknown.is_not(team_id): self._gym_team[gym_id] = team_id return self._gym_team.get(gym_id, Unknown.TINY) def gym_name(self, gym_id, gym_name=Unknown.REGULAR): """ Update and return the gym_name for a gym. """ if Unknown.is_not(gym_name): self._gym_name[gym_id] = gym_name return self._gym_name.get(gym_id, Unknown.REGULAR) def gym_desc(self, gym_id, gym_desc=Unknown.REGULAR): """ Update and return the gym_desc for a gym. """ if Unknown.is_not(gym_desc): self._gym_desc[gym_id] = gym_desc return self._gym_desc.get(gym_id, Unknown.REGULAR) def gym_image(self, gym_id, gym_image=Unknown.REGULAR): """ Update and return the gym_image for a gym. """ if Unknown.is_not(gym_image): self._gym_image[gym_id] = gym_image return self._gym_image.get(gym_id, get_image_url('icons/gym_0.png')) def clean_and_save(self): """ Cleans the cache and saves the contents if capable. """ self._clean_hist() self._save() def _save(self): """ Export the data to a more permanent location. """ pass # Mem cache isn't backed up. def _clean_hist(self): """ Clean expired objects to free up memory. """ for hist in (self._mon_hist, self._stop_hist, self._egg_hist, self._raid_hist): old = [] now = datetime.utcnow() for key, expiration in hist.iteritems(): if expiration < now: # Track expired items old.append(key) for key in old: # Remove expired events del hist[key] log.debug("Cache cleaned!")
class FacebookPageAlarm(Alarm): _defaults = { 'pokemon': { 'message': "A wild <mon_name> has appeared!", 'image': get_image_url( "monsters/<mon_id_3>_<form_id_3>.png"), 'link': "<gmaps>", 'name': "<mon_name>", 'description': "Available until <24h_time> (<time_left>).", 'caption': None }, 'pokestop': { 'message': "Someone has placed a lure on a Pokestop!", 'image': get_image_url("stop/ready.png"), 'link': "<gmaps>", 'name': "Lured Pokestop", 'description': "Lure will expire at <24h_time> (<time_left>).", 'caption': None }, 'gym': { 'message': "A Team <old_team> gym has fallen!", 'image': get_image_url("gyms/<new_team_id>.png"), 'link': "<gmaps>", 'name': "<old_team> gym fallen", 'description': "It is now controlled by <new_team>.", 'caption': None }, 'egg': { 'message': "A level <egg_lvl> raid is upcoming!", 'image': get_image_url("eggs/<egg_lvl>.png"), 'link': "<gmaps>", 'name': 'Egg', 'description': "A level <egg_lvl> raid will hatch at " "<24h_hatch_time> (<hatch_time_left>).", 'caption': None }, 'raid': { 'message': "Level <raid_lvl> raid available against <mon_name>!", 'image': get_image_url( "monsters/<mon_id_3>_000.png"), 'link': "<gmaps>", 'name': 'Raid', 'description': "The raid is available until <24h_raid_end>" " (<raid_time_left>).", 'caption': None } } # Gather settings and create alarm def __init__(self, settings): # Required Parameters self.__page_access_token = require_and_remove_key( 'page_access_token', settings, "'FacebookPage' type alarms.") self.__client = None # Optional Alarm Parameters self.__startup_message = parse_boolean( settings.pop('startup_message', "True")) # Set Alerts self.__pokemon = self.create_alert_settings( settings.pop('pokemon', {}), self._defaults['pokemon']) self.__pokestop = self.create_alert_settings( settings.pop('pokestop', {}), self._defaults['pokestop']) self.__gym = self.create_alert_settings( settings.pop('gym', {}), self._defaults['gym']) self.__egg = self.create_alert_settings( settings.pop('egg', {}), self._defaults['egg']) self.__raid = self.create_alert_settings( settings.pop('raid', {}), self._defaults['raid']) # Warn user about leftover parameters reject_leftover_parameters( settings, "Alarm level in FacebookPage alarm.") log.info("FacebookPage Alarm has been created!") # Establish connection with FacebookPage def connect(self): self.__client = facebook.GraphAPI(self.__page_access_token) # Sends a start up message on Facebook def startup_message(self): if self.__startup_message: timestamps = get_time_as_str(datetime.utcnow()) self.post_to_wall("{} - PokeAlarm has initialized!".format( timestamps[2])) log.info("Startup message sent!") # Set the appropriate settings for each alert def create_alert_settings(self, settings, default): alert = { 'message': settings.pop('message', default['message']), 'link': settings.pop('link', default['link']), 'caption': settings.pop('caption', default['caption']), 'description': settings.pop('description', default['description']), 'image': settings.pop('image', default['image']), 'name': settings.pop('name', default['name']) } reject_leftover_parameters( settings, "Alert level in FacebookPage alarm.") return alert # Post Pokemon Message def send_alert(self, alert, info): attachment = {"link": replace(alert['link'], info)} if alert['caption']: attachment['caption'] = replace(alert['caption'], info) if alert['description']: attachment['description'] = replace(alert['description'], info) if alert['image']: attachment['picture'] = replace(alert['image'], info) if alert['name']: attachment['name'] = replace(alert['name'], info) self.post_to_wall( message=replace(alert['message'], info), attachment=attachment ) # Trigger an alert based on Pokemon info def pokemon_alert(self, pokemon_info): self.send_alert(self.__pokemon, pokemon_info) # Trigger an alert based on Pokestop info def pokestop_alert(self, pokestop_info): self.send_alert(self.__pokestop, pokestop_info) # Trigger an alert based on Gym info def gym_alert(self, gym_info): self.send_alert(self.__gym, gym_info) # Trigger an alert when a raid egg has spawned (UPCOMING raid event) def raid_egg_alert(self, raid_info): self.send_alert(self.__egg, raid_info) # Trigger an alert based on Raid info def raid_alert(self, raid_info): self.send_alert(self.__raid, raid_info) # Sends a wall post to Facebook def post_to_wall(self, message, attachment=None): args = {"message": message} if attachment is not None: args['attachment'] = attachment try_sending(log, self.connect, "FacebookPage", self.__client.put_wall_post, args)
class DiscordAlarm(Alarm): _defaults = { 'monsters': { 'username': "******", 'content': "", 'icon_url': get_image_url( "regular/monsters/<mon_id_3>_<form_id_3>.png"), 'avatar_url': get_image_url( "regular/monsters/<mon_id_3>_<form_id_3>.png"), 'title': "A wild <mon_name> has appeared!", 'url': "<gmaps>", 'body': "Available until <24h_time> (<time_left>)." }, 'stops': { 'username': "******", 'content': "", 'icon_url': get_image_url("regular/stop/<lure_type_id_3>.png"), 'avatar_url': get_image_url("regular/stop/<lure_type_id_3>.png"), 'title': "Someone has placed a lure on a Pokestop!", 'url': "<gmaps>", 'body': "Lure will expire at <24h_time> (<time_left>)." }, 'gyms': { 'username': "******", 'content': "", 'icon_url': get_image_url("regular/gyms/<new_team_id>.png"), 'avatar_url': get_image_url("regular/gyms/<new_team_id>.png"), 'title': "A Team <old_team> gym has fallen!", 'url': "<gmaps>", 'body': "It is now controlled by <new_team>." }, 'eggs': { 'username': "******", 'content': "", 'icon_url': get_image_url("regular/eggs/<egg_lvl>.png"), 'avatar_url': get_image_url("regular/eggs/<egg_lvl>.png"), 'title': "Raid is incoming!", 'url': "<gmaps>", 'body': "A level <egg_lvl> raid will hatch at " "<24h_hatch_time> (<hatch_time_left>)." }, 'raids': { 'username': "******", 'content': "", 'icon_url': get_image_url("regular/monsters/<mon_id_3>_<form_id_3>.png"), 'avatar_url': get_image_url("regular/monsters/<mon_id_3>_<form_id_3>.png"), 'title': "Level <raid_lvl> raid is available against <mon_name>!", 'url': "<gmaps>", 'body': "The raid is available until " "<24h_raid_end> (<raid_time_left>)." }, 'weather': { 'username': "******", 'content': "", "icon_url": get_image_url("regular/weather/<weather_id_3>" "_<day_or_night_id_3>.png"), "avatar_url": get_image_url("regular/weather/<weather_id_3>" "_<day_or_night_id_3>.png"), "title": "The weather has changed!", "url": "<gmaps>", "body": "The weather around <lat>,<lng> has changed to <weather>!" }, 'quests': { 'username': "******", 'content': "", 'icon_url': get_image_url("regular/<quest_image>.png"), 'avatar_url': get_image_url("regular/<quest_image>.png"), 'title': "New Quest Found!", 'url': "<gmaps>", 'body': "Do this: <quest_task>\nFor this: <reward>" }, 'invasions': { 'username': "******", 'content': "", 'icon_url': get_image_url("regular/invasions/<type_id_3>.png"), 'avatar_url': get_image_url("regular/invasions/<type_id_3>.png"), 'title': "This Pokestop has been invaded by Team Rocket!", 'url': "<gmaps>", 'body': "Invasion will expire at <24h_time> (<time_left>)." } } # Gather settings and create alarm def __init__(self, mgr, settings, max_attempts, static_map_key): self._log = mgr.get_child_logger("alarms") # Required Parameters self.__webhook_url = require_and_remove_key( 'webhook_url', settings, "'Discord' type alarms.") self.__max_attempts = max_attempts # Optional Alarm Parameters self.__startup_message = parse_boolean( settings.pop('startup_message', "True")) self.__disable_embed = parse_boolean( settings.pop('disable_embed', "False")) self.__avatar_url = settings.pop('avatar_url', "") self.__map = settings.pop('map', {}) self.__static_map_key = static_map_key # Set Alert Parameters self.__monsters = self.create_alert_settings( settings.pop('monsters', {}), self._defaults['monsters']) self.__stops = self.create_alert_settings( settings.pop('stops', {}), self._defaults['stops']) self.__gyms = self.create_alert_settings( settings.pop('gyms', {}), self._defaults['gyms']) self.__eggs = self.create_alert_settings( settings.pop('eggs', {}), self._defaults['eggs']) self.__raids = self.create_alert_settings( settings.pop('raids', {}), self._defaults['raids']) self.__weather = self.create_alert_settings( settings.pop('weather', {}), self._defaults['weather']) self.__quests = self.create_alert_settings( settings.pop('quests', {}), self._defaults['quests']) self.__invasions = self.create_alert_settings( settings.pop('invasions', {}), self._defaults['invasions']) # Warn user about leftover parameters reject_leftover_parameters(settings, "'Alarm level in Discord alarm.") self._log.info("Discord Alarm has been created!") # (Re)connect with Discord def connect(self): pass # Send a message letting the channel know that this alarm has started def startup_message(self): if self.__startup_message: args = { 'url': self.__webhook_url, 'payload': { 'username': '******', 'content': 'PokeAlarm activated!' } } try_sending(self._log, self.connect, "Discord", self.send_webhook, args, self.__max_attempts) self._log.info("Startup message sent!") # Set the appropriate settings for each alert def create_alert_settings(self, settings, default): map = settings.pop('map', self.__map) alert = { 'webhook_url': settings.pop('webhook_url', self.__webhook_url), 'username': settings.pop('username', default['username']), 'avatar_url': settings.pop('avatar_url', default['avatar_url']), 'disable_embed': parse_boolean( settings.pop('disable_embed', self.__disable_embed)), 'content': settings.pop('content', default['content']), 'icon_url': settings.pop('icon_url', default['icon_url']), 'title': settings.pop('title', default['title']), 'url': settings.pop('url', default['url']), 'body': settings.pop('body', default['body']), 'fields': settings.pop('fields', []), 'map': map if isinstance(map, six.string_types) else get_static_map_url(map, self.__static_map_key) } reject_leftover_parameters(settings, "'Alert level in Discord alarm.") return alert # Send Alert to Discord def send_alert(self, alert, info): self._log.debug("Attempting to send notification to Discord.") payload = { # Usernames are limited to 32 characters 'username': replace(alert['username'], info)[:32], 'content': replace(alert['content'], info), 'avatar_url': replace(alert['avatar_url'], info), } if alert['disable_embed'] is False: payload['embeds'] = [{ 'title': replace(alert['title'], info), 'url': replace(alert['url'], info), 'description': replace(alert['body'], info), 'thumbnail': {'url': replace(alert['icon_url'], info)}, 'fields': self.replace_fields(alert['fields'], info) }] if alert['map'] is not None: coords = { 'lat': info['lat'], 'lng': info['lng'] } payload['embeds'][0]['image'] = { 'url': replace(alert['map'], coords if not isinstance(alert['map'], six.string_types) else info) } args = { 'url': replace(alert['webhook_url'], info), 'payload': payload } try_sending(self._log, self.connect, "Discord", self.send_webhook, args, self.__max_attempts) # Trigger an alert based on Pokemon info def pokemon_alert(self, pokemon_info): self._log.debug("Pokemon notification triggered.") self.send_alert(self.__monsters, pokemon_info) # Trigger an alert based on Pokestop info def pokestop_alert(self, pokestop_info): self._log.debug("Pokestop notification triggered.") self.send_alert(self.__stops, pokestop_info) # Trigger an alert based on Pokestop info def gym_alert(self, gym_info): self._log.debug("Gym notification triggered.") self.send_alert(self.__gyms, gym_info) # Trigger an alert when a raid egg has spawned (UPCOMING raid event) def raid_egg_alert(self, raid_info): self._log.debug("Raid Egg notification triggered.") self.send_alert(self.__eggs, raid_info) def raid_alert(self, raid_info): self._log.debug("Raid notification triggered.") self.send_alert(self.__raids, raid_info) # Trigger an alert based on Weather info def weather_alert(self, weather_info): self._log.debug("Weather notification triggered.") self.send_alert(self.__weather, weather_info) def quest_alert(self, quest_info): self._log.debug("Quest notification triggered.") self.send_alert(self.__quests, quest_info) def invasion_alert(self, invasion_info): self._log.debug("Invasion notification triggered.") self.send_alert(self.__invasions, invasion_info) # Send a payload to the webhook url def send_webhook(self, url, payload): self._log.debug(payload) resp = requests.post(url, json=payload, timeout=5) if resp.ok is True: self._log.debug("Notification successful (returned {})".format( resp.status_code)) else: self._log.debug("Discord response was {}".format(resp.content)) raise requests.exceptions.RequestException( "Response received {}, webhook not accepted.".format( resp.status_code)) @staticmethod def replace_fields(fields, pkinfo): replaced_fields = [] for field in fields: replaced_fields.append({ 'name': replace(field['name'], pkinfo), 'value': replace(field['value'], pkinfo), 'inline': field.get('inline', False) }) return replaced_fields
def gym_image(self, gym_id, gym_image=Unknown.REGULAR): """ Update and return the gym_image for a gym. """ if Unknown.is_not(gym_image): self._gym_image[gym_id] = gym_image return self._gym_image.get(gym_id, get_image_url('icons/gym_0.png'))
class FacebookPageAlarm(Alarm): _defaults = { 'monsters': { 'message': "A wild <mon_name> has appeared!", 'image': get_image_url("regular/monsters/<mon_id_3>_<form_id_3>.png"), 'link': "<gmaps>", 'name': "<mon_name>", 'description': "Available until <24h_time> (<time_left>).", 'caption': None }, 'stops': { 'message': "Someone has placed a lure on a Pokestop!", 'image': get_image_url("regular/stop/<lure_type_id_3>.png"), 'link': "<gmaps>", 'name': "Lured Pokestop", 'description': "Lure will expire at <24h_time> (<time_left>).", 'caption': None }, 'gyms': { 'message': "A Team <old_team> gym has fallen!", 'image': get_image_url("regular/gyms/<new_team_id>.png"), 'link': "<gmaps>", 'name': "<old_team> gym fallen", 'description': "It is now controlled by <new_team>.", 'caption': None }, 'eggs': { 'message': "A level <egg_lvl> raid is upcoming!", 'image': get_image_url("regular/eggs/<egg_lvl>.png"), 'link': "<gmaps>", 'name': 'Egg', 'description': "A level <egg_lvl> raid will hatch at " "<24h_hatch_time> (<hatch_time_left>).", 'caption': None }, 'raids': { 'message': "Level <raid_lvl> raid available against <mon_name>!", 'image': get_image_url("regular/monsters/<mon_id_3>_<form_id_3>.png"), 'link': "<gmaps>", 'name': 'Raid', 'description': "The raid is available until <24h_raid_end>" " (<raid_time_left>).", 'caption': None }, 'weather': { 'message': 'The weather has changed!', "image": get_image_url("regular/weather/<weather_id_3>" "_<day_or_night_id_3>.png"), "link": "<gmaps>", 'name': "Weather", 'description': "The weather around <lat>,<lng>" " has changed to <weather>!", 'caption': None }, 'quests': { 'message': "*New quest for <reward>*", 'image': get_image_url('regular/<quest_image>.png'), 'link': '<gmaps>', 'name': 'Quest', 'description': '<quest_task>', 'caption': None }, 'invasions': { 'message': 'This Pokestop has been invaded by Team Rocket!', 'image': get_image_url("regular/invasions/<type_id_3>.png"), 'link': '<gmaps>', 'name': 'Invasion', 'description': 'Invasion will expire at <24h_time> (<time_left>).', 'caption': None } } # Gather settings and create alarm def __init__(self, mgr, settings): self._log = mgr.get_child_logger("alarms") # Required Parameters self.__page_access_token = require_and_remove_key( 'page_access_token', settings, "'FacebookPage' type alarms.") self.__client = None # Optional Alarm Parameters self.__startup_message = parse_boolean( settings.pop('startup_message', "True")) # Set Alerts self.__monsters = self.create_alert_settings( settings.pop('monsters', {}), self._defaults['monsters']) self.__stops = self.create_alert_settings(settings.pop('stops', {}), self._defaults['stops']) self.__gyms = self.create_alert_settings(settings.pop('gyms', {}), self._defaults['gyms']) self.__eggs = self.create_alert_settings(settings.pop('eggs', {}), self._defaults['eggs']) self.__raids = self.create_alert_settings(settings.pop('raids', {}), self._defaults['raids']) self.__weather = self.create_alert_settings( settings.pop('weather', {}), self._defaults['weather']) self.__quests = self.create_alert_settings(settings.pop('quests', {}), self._defaults['quests']) self.__invasions = self.create_alert_settings( settings.pop('invasions', {}), self._defaults['invasions']) # Warn user about leftover parameters reject_leftover_parameters(settings, "Alarm level in FacebookPage alarm.") self._log.info("FacebookPage Alarm has been created!") # Establish connection with FacebookPage def connect(self): self.__client = facebook.GraphAPI(self.__page_access_token) # Sends a start up message on Facebook def startup_message(self): if self.__startup_message: timestamps = get_time_as_str(datetime.utcnow()) self.post_to_wall("{} - PokeAlarm has initialized!".format( timestamps[2])) self._log.info("Startup message sent!") # Set the appropriate settings for each alert def create_alert_settings(self, settings, default): alert = { 'message': settings.pop('message', default['message']), 'link': settings.pop('link', default['link']), 'caption': settings.pop('caption', default['caption']), 'description': settings.pop('description', default['description']), 'image': settings.pop('image', default['image']), 'name': settings.pop('name', default['name']) } reject_leftover_parameters(settings, "Alert level in FacebookPage alarm.") return alert # Post Pokemon Message def send_alert(self, alert, info): attachment = {"link": replace(alert['link'], info)} if alert['caption']: attachment['caption'] = replace(alert['caption'], info) if alert['description']: attachment['description'] = replace(alert['description'], info) if alert['image']: attachment['picture'] = replace(alert['image'], info) if alert['name']: attachment['name'] = replace(alert['name'], info) self.post_to_wall(message=replace(alert['message'], info), attachment=attachment) # Trigger an alert based on Pokemon info def pokemon_alert(self, pokemon_info): self.send_alert(self.__monsters, pokemon_info) # Trigger an alert based on Pokestop info def pokestop_alert(self, pokestop_info): self.send_alert(self.__stops, pokestop_info) # Trigger an alert based on Gym info def gym_alert(self, gym_info): self.send_alert(self.__gyms, gym_info) # Trigger an alert when a raid egg has spawned (UPCOMING raid event) def raid_egg_alert(self, raid_info): self.send_alert(self.__eggs, raid_info) # Trigger an alert based on Raid info def raid_alert(self, raid_info): self.send_alert(self.__raids, raid_info) # Trigger an alert based on Weather info def weather_alert(self, weather_info): self.send_alert(self.__weather, weather_info) # Quest Alert def quest_alert(self, quest_info): self.send_alert(self.__quests, quest_info) # Quest Alert def invasion_alert(self, invasion_info): self.send_alert(self.__invasions, invasion_info) # Sends a wall post to Facebook def post_to_wall(self, message, attachment=None): args = {"message": message} if attachment is not None: args['attachment'] = attachment try_sending(self._log, self.connect, "FacebookPage", self.__client.put_wall_post, args)