diff --git a/__init__.py b/__init__.py index 74e8a29..ef790ad 100644 --- a/__init__.py +++ b/__init__.py @@ -37,14 +37,14 @@ from typing import Optional, Tuple from bs4 import BeautifulSoup from time import sleep - from lingua_franca import load_language from ovos_bus_client import Message +from ovos_bus_client.message import dig_for_message from ovos_utils import classproperty from ovos_utils.log import LOG from ovos_utils.process_utils import RuntimeRequirements from neon_utils.user_utils import get_user_prefs, get_message_user -from neon_utils.skills.common_query_skill import \ +from ovos_workshop.skills.common_query_skill import \ CQSMatchLevel, CommonQuerySkill from neon_utils import web_utils from ovos_workshop.decorators import intent_handler @@ -84,37 +84,6 @@ def __init__(self, **kwargs): self._update_event = Event() CommonQuerySkill.__init__(self, **kwargs) - @classproperty - def runtime_requirements(self): - return RuntimeRequirements(network_before_load=False, - internet_before_load=False, - gui_before_load=False, - requires_internet=True, - requires_network=True, - requires_gui=False, - no_internet_fallback=True, - no_network_fallback=True, - no_gui_fallback=True) - - @property - def last_updated(self) -> Optional[datetime.datetime]: - if self.settings.get("lastUpdate"): - return datetime.datetime.strptime(self.settings["lastUpdate"], - '%Y-%m-%d %H:%M:%S.%f') - return None - - @property - def ww_enabled(self): - resp = self.bus.wait_for_response(Message("neon.query_wake_words_state")) - if not resp: - LOG.warning("No WW Status reported") - return None - if resp.data.get('enabled', True): - return True - return False - - # TODO: Move to __init__ after stable ovos-workshop - def initialize(self): goodbye_intent = IntentBuilder("CaffeineContentGoodbyeIntent")\ .require("goodbye").build() self.register_intent(goodbye_intent, self.handle_goodbye_intent) @@ -146,6 +115,35 @@ def initialize(self): # combine them as in get_new_info and add rocket chocolate: self._add_more_caffeine_data() + @classproperty + def runtime_requirements(self): + return RuntimeRequirements(network_before_load=False, + internet_before_load=False, + gui_before_load=False, + requires_internet=True, + requires_network=True, + requires_gui=False, + no_internet_fallback=True, + no_network_fallback=True, + no_gui_fallback=True) + + @property + def last_updated(self) -> Optional[datetime.datetime]: + if self.settings.get("lastUpdate"): + return datetime.datetime.strptime(self.settings["lastUpdate"], + '%Y-%m-%d %H:%M:%S.%f') + return None + + @property + def ww_enabled(self): + resp = self.bus.wait_for_response(Message("neon.query_wake_words_state")) + if not resp: + LOG.warning("No WW Status reported") + return None + if resp.data.get('enabled', True): + return True + return False + @intent_handler(IntentBuilder("CaffeineUpdate").require("update_caffeine")) def handle_caffeine_update(self, message): self.speak_dialog("updating") @@ -164,13 +162,13 @@ def handle_caffeine_intent(self, message): self.speak_dialog("no_drink_heard") return - if not self._update_event.isSet(): - self.speak_dialog('one_moment', private=True) + if not self._update_event.is_set(): + self.speak_dialog('one_moment') if not self._update_event.wait(30): LOG.error("Update taking more than 30s, clearing event") self._update_event.set() elif get_user_prefs(message)['response_mode'].get('hesitation'): - self.speak_dialog('one_moment', private=True) + self.speak_dialog('one_moment') if self._drink_in_database(drink): dialog, results = self._generate_drink_dialog(drink, message) @@ -179,35 +177,37 @@ def handle_caffeine_intent(self, message): else: self.speak_dialog("not_found", {'drink': drink}) - if self.neon_core: - if len(results) == 1: - if self.ww_enabled: - self.speak_dialog("how_about_more", - expect_response=True) - self.enable_intent('CaffeineContentGoodbyeIntent') - self.request_check_timeout( - self.default_intent_timeout, - ['CaffeineContentGoodbyeIntent']) - else: - self.speak_dialog("stay_caffeinated") - else: - if self.ask_yesno("more_drinks") == "yes": - self._speak_alternate_results(message, results) - self.speak_dialog("provided_by_caffeinewiz") - else: - self.speak_dialog("stay_caffeinated") + # if self.neon_core: + # if len(results) == 1: + # if self.ww_enabled: + # self.speak_dialog("how_about_more", + # expect_response=True) + # self.enable_intent('CaffeineContentGoodbyeIntent') + # self.request_check_timeout( + # self.default_intent_timeout, + # ['CaffeineContentGoodbyeIntent']) + # else: + # self.speak_dialog("stay_caffeinated") + # else: + # if self.ask_yesno("more_drinks") == "yes": + # self._speak_alternate_results(message, results) + # self.speak_dialog("provided_by_caffeinewiz") + # else: + # self.speak_dialog("stay_caffeinated") else: self.speak_dialog("not_found", {'drink': drink}) - def CQS_match_query_phrase(self, utt, message: Message = None): + def CQS_match_query_phrase(self, phrase: str): + + message = dig_for_message() try: # TODO: Language agnostic parsing here - if " of " in utt: - drink = utt.split(" of ", 1)[1] - elif " in " in utt: - drink = utt.split(" in ", 1)[1] + if " of " in phrase: + drink = phrase.split(" of ", 1)[1] + elif " in " in phrase: + drink = phrase.split(" in ", 1)[1] else: - drink = utt + drink = phrase drink = self._clean_drink_name(drink) if not drink: LOG.debug("No drink matched") @@ -225,12 +225,14 @@ def CQS_match_query_phrase(self, utt, message: Message = None): if not to_speak: # No dialog generated return None - if self.voc_match(utt, "caffeine"): + if self.voc_match(phrase, "caffeine"): + LOG.info(f"Query is about caffeine ({phrase})") conf = CQSMatchLevel.EXACT - elif matched_drink.lower() in utt.lower(): + elif matched_drink.lower() in phrase.lower(): # If the exact drink name was matched # but caffeine not requested, consider this a general match conf = CQSMatchLevel.GENERAL + LOG.debug(f"Drink ({matched_drink}) is in utterance ({phrase})") else: # We didn't match "caffeine" or an exact drink name # this request isn't for this skill @@ -239,12 +241,12 @@ def CQS_match_query_phrase(self, utt, message: Message = None): LOG.error(e) LOG.error(drink) return None - elif drink == utt: + elif drink == phrase: LOG.debug("No drink extracted from utterance") return None else: LOG.debug(f"No match for: {drink}") - if self.voc_match(utt, "caffeine"): + if self.voc_match(phrase, "caffeine"): conf = CQSMatchLevel.CATEGORY results = None to_speak = self.dialog_renderer.render("not_found", @@ -253,7 +255,7 @@ def CQS_match_query_phrase(self, utt, message: Message = None): return None LOG.info(f"results={results}, to_speak={to_speak}") user = get_message_user(message) if message else 'local' - return utt, conf, to_speak, {"user": user, + return phrase, conf, to_speak, {"user": user, "message": message.serialize() if message else None, "results": results} @@ -314,7 +316,6 @@ def handle_goodbye_intent(self, message): # Remove any awaiting confirmation try: user = get_message_user(message) - self.actions_to_confirm.pop(user) except Exception as e: LOG.error(e) @@ -463,8 +464,7 @@ def _normalize_drink_list(drink_list): try: # TODO: Check for CW and CI success if self.from_caffeine_wiz: - self.update_skill_settings({"lastUpdate": str(time_check)}, - skill_global=True) + self.update_skill_settings({"lastUpdate": str(time_check)}) if reply: self.speak_dialog("update_complete") success = True @@ -477,6 +477,16 @@ def _normalize_drink_list(drink_list): self._update_event.set() return success + def update_skill_settings(self, new_preferences: dict): + """ + Updates skill settings with the passed new_preferences + :param new_preferences: dict of updated preference values. {key: val} + """ + LOG.debug(f"Update skill settings with new: {new_preferences}") + new_settings = {**self.settings, **new_preferences} + self.settings = new_settings + self.settings.store() + def _clean_drink_name(self, drink: str) -> str: """ Normalizes an input drink name and handles known alternative names