from plugin import plugin, require import requests import datetime import random import json from colorama import Fore @require(network=True) @plugin('history') class history: """ Provides you with a random hisotry fact Enter 'history' to use: * history <event> <day> <month> Attribution: Data taken from wikipedia (CC BY-SA 3.0) with use of http://history.muffinlabs.com """ class KW: """ Inner Class for Constants """ EVENT = 'event' DAY = 'day' MONTH = 'month' KEYWORD = 'keyword' DATE_YESTERDAY = 'yesterday' DATE_TODAY = 'today' DATE_TOMORROW = 'tomorrow' ERROR = 'err' def __init__(self): self.url = "http://history.muffinlabs.com/date" self.events = ['births', 'deaths', 'events'] self.months = ['january', 'february', 'march', 'april', 'may', 'june', 'july', 'august', 'september', 'october', 'november', 'december'] self.keywords = [self.KW.DATE_YESTERDAY, self.KW.DATE_TODAY, self.KW.DATE_TOMORROW] self.MAX_LINK = 3 self.err_cfg_str = 'Error : Please pass only one argument of type {}' def __call__(self, jarvis, s): if s == 'help': self._print_help(jarvis) return jarvis.say("\ntype 'history help' for additional information") # parse and validate arguments config = self._parse_arguments(s) if config[self.KW.ERROR]: jarvis.say(config[self.KW.ERROR], Fore.RED) return # translate configurations to API recognizable api_cfg = self._parse_config(config) # query to be sent to API query = self._generate_query(api_cfg) # fetch result result = self._get_data(jarvis, query, api_cfg) if not result: return # output data self._print_result(jarvis, result) # manual for plugin def _print_help(self, jarvis): jarvis.say("\nWelcome to History!", Fore.CYAN) jarvis.say("You can use current plugin as follows:", Fore.CYAN) jarvis.say(" history <keyword> <event> <day> <month>", Fore.CYAN) jarvis.say( " <keyword> - Program uses special keywords to easily identify your query. keywords:", Fore.CYAN) jarvis.say( " * 'yesterday' - results in getting fact that happened day before today", Fore.CYAN) jarvis.say( " * 'today' - results in getting fact that happened on this day", Fore.CYAN) jarvis.say( " * 'tomorrow' - results in getting fact that happened day after today", Fore.CYAN) jarvis.say( " <event> - Argument used to specify historical fact type, which can be one of the following:", Fore.CYAN) jarvis.say(" * 'births'", Fore.CYAN) jarvis.say(" * 'deaths'", Fore.CYAN) jarvis.say(" * 'events'", Fore.CYAN) jarvis.say(" <month> - Specify month", Fore.CYAN) jarvis.say(" <day> - Specify day", Fore.CYAN) jarvis.say( "All of the arguments are optional. Not specifying results in randomization.", Fore.CYAN) jarvis.say("Example: ", Fore.CYAN) jarvis.say( " 'history 25 march birth' - birth of a random person on 25th of March", Fore.CYAN) jarvis.say( " 'history 10' - random event that happened on random month but day is 10", Fore.CYAN) jarvis.say( " 'history today' - random type of event that happened on the present day", Fore.CYAN) jarvis.say( " 'history tomorrow events' - event that occured on the day after today", Fore.CYAN) # function that maps shortened string to month # example : 'jan'->'januray', 'decem'->'december', 'github'->None def _identify_month(self, string): # if length of string is less than 3 we will not be able to identify # for example 'ju' can map to both 'june' and 'july' if len(string) < 3: return None # iterate over each month and try to find such month that contains our string # and also starts with it for month in self.months: if (string in month) and (month.index(string) == 0): return month return None # parses user given arguments and returns dictionary of configuration def _parse_arguments(self, args): # validation of arguments def __validate(main_event_type, value, cfg, validation_arr=[]): validation_arr += [main_event_type] for event_type in validation_arr: if cfg[event_type] is not None: cfg[self.KW.ERROR] = self.err_cfg_str.format(event_type) return False cfg[main_event_type] = value return True split_args = args.split() cfg = {self.KW.EVENT: None, self.KW.MONTH: None, self.KW.DAY: None, self.KW.KEYWORD: None, self.KW.ERROR: None} # iterate over the arguments an fill configurations for arg in split_args: if arg.isdigit(): if not __validate(self.KW.DAY, arg, cfg, [self.KW.KEYWORD]): return cfg elif arg in self.events: if not __validate(self.KW.EVENT, arg, cfg): return cfg elif arg in self.months: if not __validate(self.KW.MONTH, arg, cfg, [self.KW.KEYWORD]): return cfg elif arg in self.keywords: if not __validate(self.KW.KEYWORD, arg, cfg, [self.KW.DAY, self.KW.MONTH]): return cfg else: mapped_month = self._identify_month(arg) if mapped_month and not __validate(self.KW.MONTH, mapped_month, cfg): return cfg return cfg # used to further parse given configuration and validate user arguments def _parse_config(self, config): api_cfg = {self.KW.EVENT: None, self.KW.MONTH: None, self.KW.DAY: None, self.KW.KEYWORD: None, self.KW.ERROR: None} # check for events api_cfg[self.KW.EVENT] = config[self.KW.EVENT] if not api_cfg[self.KW.EVENT]: api_cfg[self.KW.EVENT] = random.choice(self.events) # track if we got date from keywords api_cfg[self.KW.KEYWORD] = False # check for keywords if config[self.KW.KEYWORD]: # if keywords present we already have date api_cfg[self.KW.KEYWORD] = True # today's date date = datetime.datetime.now() # timestamp of one day timestamp_day = datetime.timedelta(days=1) if config[self.KW.KEYWORD] == self.KW.DATE_YESTERDAY: # if keyword was yesterday substitue timestamp from date date -= timestamp_day elif config[self.KW.KEYWORD] == self.KW.DATE_TOMORROW: # if keyword was yesterday add timestamp to date date += timestamp_day api_cfg[self.KW.DAY] = date.day api_cfg[self.KW.MONTH] = date.month else: # if keywords were not passed we need to find/randomize date # check for month api_cfg[self.KW.MONTH] = config[self.KW.MONTH] if not api_cfg[self.KW.MONTH]: api_cfg[self.KW.MONTH] = random.choice(self.months) # check for day api_cfg[self.KW.DAY] = config[self.KW.DAY] if not api_cfg[self.KW.DAY]: api_cfg[self.KW.DAY] = random.randint(1, 29) return api_cfg # generates query to be sent over web to given API def _generate_query(self, api_cfg): day = api_cfg[self.KW.DAY] if api_cfg[self.KW.KEYWORD]: # if keyword exists, then we are taking data from datetime an month is type of int month = api_cfg[self.KW.MONTH] else: # otherwise it's string month = self.months.index(api_cfg[self.KW.MONTH]) + 1 # url = api.com/date/<month>/<day> query_str = '{}/{}/{}'.format(self.url, month, day) return query_str # send request and retrieves data from API def _get_data(self, jarvis, query, api_cfg): data = None try: jarvis.spinner_start('Searching through history ') # send request response = requests.get(query) # parse into json result = response.json() # randomly et one of the facts facts_arr = result['data'][api_cfg[self.KW.EVENT].capitalize()] fact = random.choice(facts_arr) # generate data from result data = { 'date': result['date'], 'type': api_cfg[self.KW.EVENT], 'year': fact['year'], 'text': fact['text'], 'links': fact['links'] } jarvis.spinner_stop() except: jarvis.spinner_stop( message="\nTask execution Failed!", color=Fore.RED) jarvis.say( "Please check that arguments are correct and day of month is valid!", Fore.RED) jarvis.say( "If error occures again, then API might have crashed. Try again later.\n", Fore.RED) finally: return data # prints result of query in a human readable way def _print_result(self, jarvis, result): # first line of output contains date of fact jarvis.say('\nDate : {} of {}'.format( result['date'], result['year']), Fore.BLUE) # second line contains information jarvis.say('{} : {}'.format(result['type'], result['text']), Fore.BLUE) # next lines will be links to external sources jarvis.say('External links : ', Fore.BLUE) result['links'] = result['links'][:self.MAX_LINK] for i in range(len(result['links'])): jarvis.say(' {}). {}'.format( i + 1, result['links'][i]['link']), Fore.BLUE)