def train(parser, args): if bool(args.input_files) == bool(args.data): parser.error( 'You must specify one of input_files or --data (but not both)') cont = IntentContainer(args.intent_cache) if args.data: cont.apply_training_args(args.data) else: for fn in args.input_files: obj_name, ext = splitext(basename(fn)) if ext == '.intent': cont.load_intent(obj_name, fn) elif ext == '.entity': cont.load_entity(obj_name, fn) else: parser.error('Unknown file extension: {}'.format(ext)) kwargs = inspect.signature(cont.train).bind(*(args.args or [])).arguments kwargs.update(args.kwargs or {}) kwargs.setdefault('debug', True) kwargs.setdefault('single_thread', args.single_thread) kwargs.setdefault('force', args.force) if cont.train(**kwargs): return 0 return 10 # timeout
def create_container(): container = IntentContainer('intent_cache') dir = os.getenv('VOCAB_DIR', '/qabot/vocab/en-us/') for file in os.listdir(dir): print(file) if file.endswith(".intent"): container.load_intent(basename(file), join(dir, file)) elif file.endswith(".entity"): container.load_entity(basename(file), join(dir, file)) container.train() return container
class PadatiousFileIntent(IntentPlugin): """Interface for Padatious intent engine""" def __init__(self, rt): super().__init__(rt) self.container = IntentContainer( join(rt.paths.user_config, 'intent_cache')) def register(self, intent: Any, skill_name: str, intent_id: str): file_name = join(self.rt.paths.skill_locale(skill_name), intent + '.intent') self.container.load_intent(name=intent_id, file_name=file_name) def register_entity(self, entity: Any, entity_id: str, skill_name: str): file_name = join(self.rt.paths.skill_locale(skill_name), entity + '.intent') self.container.load_intent(name=entity_id, file_name=file_name) def unregister(self, intent_id: str): self.container.remove_intent(intent_id) def unregister_entity(self, entity_id: str): self.container.remove_entity(entity_id) def compile(self): log.info('Training...') self.container.train() log.info('Training complete!') def calc_intents(self, query): return [ IntentMatch(intent_id=data.name, confidence=data.conf, matches=data.matches, query=query) for data in self.container.calc_intents(query) ]
class DuckDuckGoSkill(CommonQuerySkill): def __init__(self): super().__init__() self.translator = google_translator() self.tx_cache = {} # avoid translating twice self.duck_cache = {} self.rake = Rake() # only english for now # for usage in tell me more / follow up questions self.idx = 0 self.results = [] self.image = None # subparser, intents just for this skill # not part of main intent service intent_cache = expanduser( self.config_core['padatious']['intent_cache']) self.intents = IntentContainer(intent_cache) def initialize(self): self.load_intents() # check for conflicting skills just in case # done after all skills loaded to ensure proper shutdown self.add_event("mycroft.skills.initialized", self.blacklist_default_skill) def load_intents(self): # TODO intents for other infobox fields for intent in ["who", "birthdate"]: path = self.find_resource(intent + '.intent', "locale") if path: self.intents.load_intent(intent, path) self.intents.train(single_thread=True) def get_intro_message(self): # blacklist conflicting skills on install self.blacklist_default_skill() def blacklist_default_skill(self): # load the current list of already blacklisted skills blacklist = self.config_core["skills"]["blacklisted_skills"] # check the folder name (skill_id) of the skill you want to replace skill_id = "mycroft-fallback-duck-duck-go.mycroftai" # add the skill to the blacklist if skill_id not in blacklist: self.log.debug("Blacklisting official mycroft skill") blacklist.append(skill_id) # load the user config file (~/.mycroft/mycroft.conf) conf = LocalConf(USER_CONFIG) if "skills" not in conf: conf["skills"] = {} # update the blacklist field conf["skills"]["blacklisted_skills"] = blacklist # save the user config file conf.store() # tell the intent service to unload the skill in case it was loaded already # this should avoid the need to restart self.bus.emit(Message("detach_skill", {"skill_id": skill_id})) def stop(self): self.gui.release() # intents @intent_handler("search_duck.intent") def handle_search(self, message): query = message.data["query"] summary = self.ask_the_duck(query) if summary: self.speak_result() else: answer, _, _ = self.parse_subintents(query) if answer: self.speakr(answer) else: self.speak_dialog("no_answer") @intent_handler( IntentBuilder("DuckMore").require("More").require("DuckKnows")) def handle_tell_more(self, message): """ Follow up query handler, "tell me more".""" query = message.data["DuckKnows"] data, related_queries = self.get_infobox(query) # TODO maybe do something with the infobox data ? self.speak_result() # common query def parse_subintents(self, utt): # Get response from intents, this is a subparser that will handle # queries about the infobox returned by duckduckgo # eg. when was {person} born match = self.intents.calc_intent(utt) level = CQSMatchLevel.CATEGORY data = match.matches intent = match.name score = match.conf data["intent"] = intent data["score"] = score query = utt if score > 0.8: level = CQSMatchLevel.EXACT elif score > 0.5: level = CQSMatchLevel.CATEGORY elif score > 0.3: level = CQSMatchLevel.GENERAL else: intent = None self.log.debug("DuckDuckGo Intent: " + str(intent)) if "person" in data: query = data["person"] summary = self.ask_the_duck(query) answer = summary if summary: answer = self.results[0] infobox, related_queries = self.get_infobox(query) self.log.debug("DuckDuckGo infobox: " + str(infobox)) data["infobox"] = infobox data["related_queries"] = related_queries if intent == "birthdate": answer = infobox.get("born") data["query"] = query data["answer"] = answer data["image"] = self.image if not answer: level = CQSMatchLevel.GENERAL return answer, level, data def CQS_match_query_phrase(self, utt): self.log.debug("DuckDuckGo query: " + utt) answer, match, data = self.parse_subintents(utt) if answer: self.idx += 1 return (utt, match, answer, data) # extract most relevant keyword utt = self.translate(utt, "en", self.lang) keywords = self.rake.extract_keywords(utt) self.log.debug("Extracted keywords: " + str(keywords)) # TODO better selection / merging of top keywords with same # confidence?? for kw in keywords: query = kw[0] self.log.debug("Selected keyword: " + query) summary = self.ask_the_duck(query, translate=False) if summary: self.idx += 1 return (utt, CQSMatchLevel.GENERAL, self.results[0], { 'query': query, 'answer': self.results[0], "keywords": keywords, "image": self.image }) def CQS_action(self, phrase, data): """ If selected show gui """ self.display_ddg(data["answer"], data["image"]) # duck duck go api def ask_the_duck(self, query, translate=True): if translate: # Automatic translation to English utt = self.translate(query, "en", self.lang) else: utt = query # cache so we dont hit the api twice for the same query if query not in self.duck_cache: self.duck_cache[query] = requests.get("https://api.duckduckgo.com", params={ "format": "json", "q": utt }).json() data = self.duck_cache[query] # GUI self.gui.clear() # clear previous answer just in case title = data.get("Heading") self.image = data.get("Image", "") # summary summary = data.get("AbstractText") if not summary: return None self.log.debug("DuckDuckGo answer: " + summary) # context for follow up questions # TODO intents for this, with this context intents can look up all data self.set_context("DuckKnows", query) self.idx = 0 self.results = summary.split(". ") return summary def display_ddg(self, summary, image): if image.startswith("/"): image = "https://duckduckgo.com" + image self.gui['summary'] = summary self.gui['imgLink'] = image self.gui.show_page("DuckDelegate.qml", override_idle=60) def speak_result(self): if self.idx + 1 > len(self.results): # TODO ask user if he wants to hear about related topics self.speak_dialog("thats all") self.remove_context("ddg") self.idx = 0 else: if self.image: self.display_ddg(self.results[self.idx], self.image) self.speak(self.results[self.idx]) self.idx += 1 def get_infobox(self, query): if query not in self.duck_cache: self.ask_the_duck(query) data = self.duck_cache[query] # info related_topics = [t.get("Text") for t in data.get("RelatedTopics", [])] infobox = {} infodict = data.get("Infobox") or {} for entry in infodict.get("content", []): k = entry["label"].lower().strip() infobox[k] = entry["value"] return infobox, related_topics def translate(self, utterance, lang_tgt=None, lang_src="en"): lang_tgt = lang_tgt or self.lang # if langs are the same do nothing if not lang_tgt.startswith(lang_src): if lang_tgt not in self.tx_cache: self.tx_cache[lang_tgt] = {} # if translated before, dont translate again if utterance in self.tx_cache[lang_tgt]: # get previous translated value translated_utt = self.tx_cache[lang_tgt][utterance] else: # translate this utterance translated_utt = self.translator.translate( utterance, lang_tgt=lang_tgt, lang_src=lang_src).strip() # save the translation if we need it again self.tx_cache[lang_tgt][utterance] = translated_utt self.log.debug("translated {src} -- {tgt}".format( src=utterance, tgt=translated_utt)) else: translated_utt = utterance.strip() return translated_utt
class PadatiousExtractor(IntentExtractor): keyword_based = False def __init__(self, cache_dir=None, *args, **kwargs): super().__init__(*args, **kwargs) # TODO xdg data_dir data_dir = expanduser(self.config.get("data_dir", "~/.padatious")) cache_dir = cache_dir or join(data_dir, "padatious") self.lock = Lock() self.container = IntentContainer(cache_dir) self.registered_intents = [] def detach_intent(self, intent_name): if intent_name in self.registered_intents: LOG.debug("Detaching padatious intent: " + intent_name) with self.lock: self.container.remove_intent(intent_name) self.registered_intents.remove(intent_name) def detach_skill(self, skill_id): LOG.debug("Detaching padatious skill: " + str(skill_id)) remove_list = [i for i in self.registered_intents if skill_id in i] for i in remove_list: self.detach_intent(i) def register_entity(self, entity_name, samples=None, reload_cache=True): samples = samples or [entity_name] with self.lock: self.container.add_entity(entity_name, samples, reload_cache=reload_cache) def register_intent(self, intent_name, samples=None, reload_cache=True): samples = samples or [intent_name] if intent_name not in self._intent_samples: self._intent_samples[intent_name] = samples else: self._intent_samples[intent_name] += samples with self.lock: self.container.add_intent(intent_name, samples, reload_cache=reload_cache) self.registered_intents.append(intent_name) def register_entity_from_file(self, entity_name, file_name, reload_cache=True): with self.lock: self.container.load_entity(entity_name, file_name, reload_cache=reload_cache) def register_intent_from_file(self, intent_name, file_name, single_thread=True, timeout=120, reload_cache=True, force_training=True): try: with self.lock: self.container.load_intent(intent_name, file_name, reload_cache=reload_cache) self.registered_intents.append(intent_name) success = self._train(single_thread=single_thread, timeout=timeout, force_training=force_training) if success: LOG.debug(file_name + " trained successfully") else: LOG.error(file_name + " FAILED TO TRAIN") except Exception as e: LOG.exception(e) def _get_remainder(self, intent, utterance): if intent["name"] in self.intent_samples: return get_utterance_remainder( utterance, samples=self.intent_samples[intent["name"]]) return utterance def calc_intent(self, utterance, min_conf=None): min_conf = min_conf or self.config.get("padatious_min_conf", 0.65) utterance = utterance.strip().lower() with self.lock: intent = self.container.calc_intent(utterance).__dict__ if intent["conf"] < min_conf: return { "intent_type": "unknown", "entities": {}, "conf": 0, "intent_engine": "padatious", "utterance": utterance, "utterance_remainder": utterance } intent["utterance_remainder"] = self._get_remainder(intent, utterance) intent["entities"] = intent.pop("matches") intent["intent_engine"] = "padatious" intent["intent_type"] = intent.pop("name") intent["utterance"] = intent.pop("sent") if isinstance(intent["utterance"], list): intent["utterance"] = " ".join(intent["utterance"]) return intent def intent_scores(self, utterance): utterance = utterance.strip().lower() intents = [i.__dict__ for i in self.container.calc_intents(utterance)] for idx, intent in enumerate(intents): intent["utterance_remainder"] = self._get_remainder( intent, utterance) intents[idx]["entities"] = intents[idx].pop("matches") intents[idx]["intent_type"] = intents[idx].pop("name") intent["intent_engine"] = "padatious" intent["utterance"] = intent.pop("sent") if isinstance(intents[idx]["utterance"], list): intents[idx]["utterance"] = " ".join(intents[idx]["utterance"]) return intents def calc_intents(self, utterance, min_conf=None): min_conf = min_conf or self.config.get("padatious_min_conf", 0.65) utterance = utterance.strip().lower() bucket = {} for ut in self.segmenter.segment(utterance): intent = self.calc_intent(ut) if intent["conf"] < min_conf: bucket[ut] = None else: bucket[ut] = intent return bucket def calc_intents_list(self, utterance): utterance = utterance.strip().lower() bucket = {} for ut in self.segmenter.segment(utterance): bucket[ut] = self.filter_intents(ut) return bucket def manifest(self): # TODO vocab, skill ids, intent_data return {"intent_names": self.registered_intents} def _train(self, single_thread=True, timeout=120, force_training=True): with self.lock: return self.container.train(single_thread=single_thread, timeout=timeout, force=force_training, debug=True)
class CountriesSkill(CommonQuerySkill): def __init__(self): super(CountriesSkill, self).__init__() if "map_style" not in self.settings: self.settings["map_style"] = "ortho" self.countries_data = {} self.country_codes = {} self.regions = [ u'Asia', u'Europe', u'Africa', u'Oceania', u'Americas', u'Polar' ] self.subregions = [ u'Southern Asia', u'Northern Europe', u'Southern Europe', u'Northern Africa', u'Polynesia', u'Middle Africa', u'Caribbean', u'South America', u'Western Asia', u'Australia and New Zealand', u'Western Europe', u'Eastern Europe', u'Central America', u'Western Africa', u'Northern America', u'Southern Africa', u'Eastern Africa', u'South-Eastern Asia', u'Eastern Asia', u'Melanesia', u'Micronesia', u'Central Asia' ] self.get_country_data() intent_cache = expanduser( self.config_core['padatious']['intent_cache']) self.intents = IntentContainer(intent_cache) def initialize(self): if cartopy is None: self.log.info( "Map plots are disabled, additional requirements needed") self.log.info( "https://scitools.org.uk/cartopy/docs/latest/installing.html") self.load_intents() # CommonQuery Padatious subparser def load_intents(self): for intent in [ "country_area", "country_borders", "country_capital", "country_currency", "country_in_region", "country_languages", "country_num", "country_population", "country_region", "country_timezones", "denonym", "where_language_spoken" ]: path = self.find_resource(intent + '.intent', "vocab") if path: self.intents.load_intent(intent, path) self.intents.train(single_thread=True) def intent2answer(self, intent, data): # Get response from intents response = None if intent == "country_area": response = self.handle_country_area(data) elif intent == "country_timezones": response = self.handle_country_timezones(data) elif intent == "where_language_spoken": response = self.handle_language_where(data) elif intent == "denonym": response = self.handle_country_denonym(data) elif intent == "country_region": response = self.handle_country_where(data) elif intent == "country_population": response = self.handle_country_population(data) elif intent == "country_borders": response = self.handle_country_borders(data) elif intent == "country_capital": response = self.handle_country_capital(data) elif intent == "country_currency": response = self.handle_country_currency(data) elif intent == "country_in_region": response = self.handle_country_in_region(data) elif intent == "country_languages": response = self.handle_country_languages(data) elif intent == "country_num": response = self.handle_country_number(data) return response def CQS_match_query_phrase(self, phrase): """Analyze phrase to see if it is a play-able phrase with this skill. Needs to be implemented by the skill. Arguments: phrase (str): User phrase, "What is an aardwark" Returns: (match, CQSMatchLevel[, callback_data]) or None: Tuple containing a string with the appropriate matching phrase, the PlayMatch type, and optionally data to return in the callback if the match is selected. """ response = None match = self.intents.calc_intent(phrase) level = CQSMatchLevel.CATEGORY data = match.matches intent = match.name score = match.conf data["intent"] = intent data["score"] = score if score > 0.8: level = CQSMatchLevel.EXACT elif score > 0.5: level = CQSMatchLevel.CATEGORY elif score > 0.3: level = CQSMatchLevel.GENERAL else: intent = None if intent: # Validate extracted entities country = data.get("country") region = data.get("region") language = data.get("language") if country: data["query"] = country # ensure we really have a country name response = self.dialog_renderer.render("bad_country", {}) match, score = match_one(country.lower(), list(self.countries_data.keys())) self.log.debug("Country fuzzy match: {n}, Score: {s}".format( n=match, s=score)) if score > 0.5: country = match data.update(self.countries_data[country]) else: countries = self.search_country(country) if not len(countries) > 0: level = CQSMatchLevel.GENERAL else: country = countries[0]["name"] data.update(countries[0]) # TODO disambiguation if len(countries) > 1: data["disambiguation"] = countries[1:] self.log.debug("multiple matches found: " + str([c["name"] for c in countries])) data["country"] = country # normalized from match if language: data["query"] = language # ensure we really have a language name words = language.split(" ") clean_up = ["is"] # remove words commonly caught by mistake in padatious language = " ".join( [word for word in words if word not in clean_up]) lang_code = langcodes.find_name( 'language', language, langcodes.standardize_tag(self.lang)) lang_code = str(lang_code) self.log.debug("Detected lang code: " + lang_code) if not lang_code: return None data["lang_code"] = lang_code # TODO countries = self.search_country_by_language(lang_code) data["country_list"] = countries if region: data["query"] = region # ensure we really have a region name response = self.dialog_renderer.render("bad_region") countries = None match, score = match_one(region, self.regions) data["region_score"] = score if score > 0.5: region = match countries = self.search_country_by_region(region) match, score2 = match_one(region, self.subregions) data["subregion_score"] = score2 if score2 > score: region = match countries = self.search_country_by_subregion(region) if score > 0.8 and not country: level = CQSMatchLevel.EXACT elif score > 0.5 and not country: level = CQSMatchLevel.CATEGORY elif score > 0.3 and not country: level = CQSMatchLevel.GENERAL data["region"] = region self.log.debug("Detected region: " + region) data["country_list"] = countries # Get response from intents response = self.intent2answer(intent, data) or response if response: return (phrase, level, response, data) return None def CQS_action(self, phrase, data): """Take additional action IF the skill is selected. The speech is handled by the common query but if the chosen skill wants to display media, set a context or prepare for sending information info over e-mail this can be implemented here. Args: phrase (str): User phrase uttered after "Play", e.g. "some music" data (dict): Callback data specified in match_query_phrase() """ projection = cartopy.crs.PlateCarree() if data.get("country"): title = data["country"] if self.settings["map_style"] == "ortho": country = self.countries_data[data["country"].lower()] lat = country["lat"] lon = country["long"] projection = cartopy.crs.Orthographic(lon, lat) image = self.plot_country(data["country"], projection=projection) self.gui.show_image(image, fill='PreserveAspectFit', title=title) elif data.get("region"): title = data["region"] countries = data["country_list"] if self.settings["map_style"] == "ortho": country = self.countries_data[countries[0]["name"].lower()] lat = country["lat"] lon = country["long"] projection = cartopy.crs.Orthographic(lon, lat) image = self.plot_region(data["region"], projection=projection) self.gui.show_image(image, fill='PreserveAspectFit', title=title) elif data.get("country_list"): countries = data["country_list"] # TODO allow this somehow, will not show all countries #if self.settings["map_style"] == "ortho": # country = self.countries_data[countries[0]["name"].lower()] # lat = country["lat"] # lon = country["long"] # projection = cartopy.crs.Orthographic(lon, lat) title = data.get("region") \ or data.get("language") \ or data.get("lang_code") \ or " " countries = [c["name"] for c in countries] image = self.plot_countries(countries, projection=projection, name=title, region=data.get("region")) self.gui.show_image(image, fill='PreserveAspectFit', title=title, caption=", ".join(countries)) # gui @staticmethod def _get_country_geometry(query, region=None, min_score=0.7): best_score = 0 best_match = None shapename = 'admin_0_countries' countries_shp = cartopy.io.shapereader.natural_earth( resolution='110m', category='cultural', name=shapename) for country in cartopy.io.shapereader.Reader(countries_shp).records(): country_name = country.attributes['NAME'].lower() country_long_name = country.attributes['NAME_LONG'].lower() reg = country.attributes["REGION_WB"].lower() subregion = country.attributes["SUBREGION"].lower() continent = country.attributes["CONTINENT"].lower() match, score = match_one(query.lower(), [country_long_name, country_name]) if region: _, score2 = match_one(region.lower(), [reg, subregion, continent]) score = (score + score2) / 2 if score > best_score: best_score = score best_match = country.geometry if best_score < min_score: best_match = None return best_match def _get_region_countries(self, query, min_score=0.7): countries = [] region, score = match_one(query, self.regions) if score > min_score - 0.15: countries = self.search_country_by_region(region) if score < min_score: region, score = match_one(query, self.subregions) if score > min_score: countries = self.search_country_by_subregion(region) return [c["name"] for c in countries] @staticmethod def _get_region_geometries(query, min_score=0.8): shapename = 'admin_0_countries' countries_shp = cartopy.io.shapereader.natural_earth( resolution='110m', category='cultural', name=shapename) geoms = [] for country in cartopy.io.shapereader.Reader(countries_shp).records(): continent = country.attributes["CONTINENT"].lower() region = country.attributes["REGION_WB"].lower() subregion = country.attributes["SUBREGION"].lower() match, score = match_one(query.lower(), [region, subregion, continent]) if score > min_score or \ (query.lower() in region.lower() and score >= 0.5): geoms.append(country.geometry) return geoms def plot_country(self, query, projection=None, rgb=None): if cartopy is None: return output = join(gettempdir(), query + self.settings["map_style"] + ".png") if isfile(output): return output ax = plt.axes(projection=projection) ax.stock_img() ax.coastlines() r, g, b = rgb or (255, 0, 0) color = (r / 255, g / 255, b / 255) geometry = self._get_country_geometry(query) if geometry: geometries = [geometry] ax.add_geometries(geometries, cartopy.crs.PlateCarree(), facecolor=color) plt.savefig(output, bbox_inches='tight', facecolor="black") plt.close() return output return None def plot_countries(self, countries, projection=None, rgb=None, name=None, region=None): if cartopy is None: return name = name or "_".join([c[:2] for c in countries]) output = join(gettempdir(), name + self.settings["map_style"] + "_countries.png") if isfile(output): return output projection = projection or cartopy.crs.PlateCarree() ax = plt.axes(projection=projection) ax.stock_img() ax.coastlines() r, g, b = rgb or (255, 0, 0) color = (r / 255, g / 255, b / 255) geometries = [] for query in countries: geometry = self._get_country_geometry(query, region) if geometry: geometries.append(geometry) ax.add_geometries(geometries, cartopy.crs.PlateCarree(), facecolor=color) plt.savefig(output, bbox_inches='tight', facecolor="black") plt.close() return output def plot_region(self, query, projection=None, rgb=None): if cartopy is None: return output = join(gettempdir(), query + self.settings["map_style"] + "_region.png") if isfile(output): return output ax = plt.axes(projection=projection) ax.stock_img() ax.coastlines() r, g, b = rgb or (255, 0, 0) color = (r / 255, g / 255, b / 255) geometries = self._get_region_geometries(query) if not geometries: countries = self._get_region_countries(query) return self.plot_countries(countries, projection, (r, g, b), name=query, region=query) ax.add_geometries(geometries, cartopy.crs.PlateCarree(), facecolor=color) plt.savefig(output, bbox_inches='tight', facecolor="black") plt.close() return output # intents def handle_country_where(self, data): name = data["country"] region = data["region"] sub = data["subregion"] if region in sub: r = sub else: r = sub + ", " + region return self.dialog_renderer.render("country_location", { "country": name, "region": r }) def handle_country_currency(self, data): country = data["country"] coins = self.countries_data[country]["currencies"] coins = ", ".join([self.pretty_currency(c) for c in coins]) return self.dialog_renderer.render("currency", { "country": country, "coin": coins }) def handle_country_in_region(self, data): region = data["region"] countries = data["country_list"] if len(countries): return "; ".join([c["name"] for c in countries]) else: return self.dialog_renderer.render("bad_region") def handle_language_where(self, data): lang_code = data["lang_code"] lang_name = data["language"] countries = data["country_list"] if len(countries): # TODO dialog files return ", ".join([c["name"] for c in countries]) else: return self.dialog_renderer.render("bad_country") def handle_country_languages(self, data): country = data["country"] if country in self.countries_data.keys(): langs = self.countries_data[country]["languages"] return ", ".join([lang for lang in langs]) else: return self.dialog_renderer.render("bad_country") def handle_country_timezones(self, data): country = data["country"] if country in self.countries_data.keys(): timezones = ", ".join(self.countries_data[country]["timezones"]) return self.dialog_renderer.render("timezones", { "country": country, "timezones": timezones }) else: return self.dialog_renderer.render("bad_country") def handle_country_area(self, data): country = data["country"] if country in self.countries_data.keys(): area = self.countries_data[country]["area"] # TODO convert units area = pronounce_number(float(area), lang=self.lang) return self.dialog_renderer.render("area", { "country": country, "number": area }) else: return self.dialog_renderer.render("bad_country") def handle_country_population(self, data): country = data["country"] if country in self.countries_data.keys(): population = self.countries_data[country]["population"] area = pronounce_number(int(population), lang=self.lang) return self.dialog_renderer.render("population", { "country": country, "number": area }) else: return self.dialog_renderer.render("bad_country") def handle_country_borders(self, data): country = data["country"] if country in self.countries_data.keys(): borders = self.countries_data[country]["borders"] borders = ", ".join([self.country_codes[b] for b in borders]) return self.dialog_renderer.render("borders", { "country": country, "borders": borders }) else: return self.dialog_renderer.render("bad_country") def handle_country_capital(self, data): country = data["country"] if country in self.countries_data.keys(): capital = self.countries_data[country]["capital"] return self.dialog_renderer.render("capital", { "country": country, "capital": capital }) else: return self.dialog_renderer.render("bad_country") def handle_country_denonym(self, data): country = data["country"] if country in self.countries_data.keys(): denonym = self.countries_data[country]["demonym"] return self.dialog_renderer.render("denonym", { "country": country, "denonym": denonym }) else: return self.dialog_renderer.render("bad_country") def handle_country_number(self, data): number = pronounce_number(len(self.countries_data), lang=self.lang) return self.dialog_renderer.render("country_number", {"number": number}) # country api @staticmethod def pretty_currency(currency_code): currency_code = currency_code.upper() if currency_code in CURRENCY.keys(): return CURRENCY[currency_code].name return currency_code @staticmethod def get_all_countries(): return CountryApi.get_all() def get_country_data(self): countries = self.get_all_countries() for c in countries: name = c["name"].lower() self.countries_data[name] = {} self.countries_data[name]["timezones"] = c["timezones"] self.countries_data[name]["demonym"] = c["demonym"] self.countries_data[name]["currencies"] = c["currencies"] self.countries_data[name]["alpha2Code"] = c["alpha2Code"] self.country_codes[c["alpha2Code"]] = name self.countries_data[name]["alpha3Code"] = c["alpha3Code"] self.country_codes[c["alpha3Code"]] = name self.countries_data[name]["area"] = str(c["area"]) self.countries_data[name]["languages"] = [ langcodes.LanguageData(language=l).language_name() for l in c["languages"] ] self.countries_data[name]["lang_codes"] = [ langcodes.standardize_tag(l) for l in c["languages"] ] self.countries_data[name]["capital"] = c["capital"] self.countries_data[name]["borders"] = c["borders"] self.countries_data[name]["nativeName"] = c["nativeName"] self.countries_data[name]["population"] = str(c["population"]) self.countries_data[name]["region"] = c["region"] self.countries_data[name]["subregion"] = c["subregion"] if len(c["latlng"]): self.countries_data[name]["lat"], \ self.countries_data[name]["long"] = c["latlng"] @staticmethod def search_country(name): try: return CountryApi.get_countries_by_name(name) except: return [] @staticmethod def search_country_by_code(code): try: return CountryApi.get_countries_by_country_codes([code]) except: return [] @staticmethod def search_country_by_language(lang_code): try: return CountryApi.get_countries_by_language(lang_code) except: return [] @staticmethod def search_country_by_region(region): try: return CountryApi.get_countries_by_region(region) except: return [] @staticmethod def search_country_by_subregion(subregion): try: return CountryApi.get_countries_by_subregion(subregion) except: return []