diff --git a/CHANGELOG.md b/CHANGELOG.md index 235e982..b772a53 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,30 @@ # Changelog +## [2.2.0] - 28-08-2021 + +### Breacking Changes +- New parameter "image"; old parameters "url" and "file" don't work anymore + +### Improvments +- Improvments in management of companion app messages: it's possible to send images, TTS messages and more + +### Added +- Added management of Email, Pushover and Pushbullet messages +- Initial support of Discord messages +- Google Home: added resume stream function after TTS (experimental) +- Messages to multiple notification services + +## [2.1.0] - 17-11-2020 + +### Breacking Changes +- Nothing + +### Improvments +- Various fixes + +### Added +- New TTS added: Reverso TTS e Goggle Cloud + ## [2.0.0] - 30-10-2020 ### Breacking Changes diff --git a/apps/notifier/alexa_manager.py b/apps/notifier/alexa_manager.py index 3912f39..99796a2 100644 --- a/apps/notifier/alexa_manager.py +++ b/apps/notifier/alexa_manager.py @@ -297,7 +297,7 @@ def speak(self, alexa): self.lg(f"REMOVE [NONE] VALUE: {type(alexa)} value {alexa}") default_restore_volume = float(self.get_state(self.args.get("default_restore_volume"))) / 100 volume = float(alexa.get("volume", default_restore_volume)) - message = str(alexa.get("message", alexa.get("message_tts"))) + message = str(alexa.get("message", alexa.get("message_tts", ""))) alexa_player = self.player_get(alexa.get("media_player", self.get_state(self.alexa_sensor_media_player))) alexa_type = ( str(alexa.get("type", self.get_state(self.alexa_type))).lower().replace("dropin", "dropin_notification") @@ -408,7 +408,7 @@ def find_speechcon(self, value): def player_get(self, user_player): media_player = [] - user_player = self.converti(str(user_player.lower())) + user_player = user_player if isinstance(user_player, list) else self.converti(str(user_player.lower())) for mpu in user_player: # MediaPlayerUser if "test" in mpu: media_player.extend(self.player_alexa) diff --git a/apps/notifier/gh_manager.py b/apps/notifier/gh_manager.py index e6f2e42..1e44729 100644 --- a/apps/notifier/gh_manager.py +++ b/apps/notifier/gh_manager.py @@ -23,8 +23,10 @@ class GH_Manager(hass.Hass): def initialize(self)->None: + #self.gh_wait_time = globals.get_arg(self.args, "gh_wait_time") self.gh_wait_time = self.args["gh_wait_time"] self.gh_select_media_player = self.args["gh_select_media_player"] + self.queue = Queue(maxsize=0) self._when_tts_done_callback_queue = Queue() t = Thread(target=self.worker) @@ -44,6 +46,7 @@ def check_volume(self, gh_volume): gh = [] for entity, state in media_state.items(): friendly_name = state["attributes"].get("friendly_name") + for item in gh_volume: if "gruppo" not in str(item).lower() and item == friendly_name: gh.append(entity) @@ -60,6 +63,19 @@ def volume_get(self, media_player:list, volume: float): self.dict_volumes[i] = self.get_state(i, attribute="volume_level", default=volume) return self.dict_volumes + def mediastate_get(self, media_player:list, volume: float): + self.dict_info_mplayer = {} + for i in media_player: + self.dict_info_mplayer[i] = {} + for i in media_player: + #self.dict_info_mplayer[i]['volume'] = self.get_state(i, attribute="volume_level", default=volume) + self.dict_info_mplayer[i]['state'] = self.get_state(i, default='idle') + self.dict_info_mplayer[i]['media_id'] = self.get_state(i, attribute="media_content_id", default='') + self.dict_info_mplayer[i]['media_type'] = self.get_state(i, attribute="media_content_type", default='') + self.dict_info_mplayer[i]['app_name'] = self.get_state(i, attribute="app_name", default='') + self.dict_info_mplayer[i]['authSig'] = self.get_state(i, attribute="authSig", default='') + return self.dict_info_mplayer + def replace_regular(self, text: str, substitutions: list): for old,new in substitutions: text = re.sub(old, new, str(text).strip()) @@ -72,7 +88,10 @@ def speak(self, google, gh_mode: bool, gh_notifier: str): """Speak the provided text through the media player""" gh_player = self.check_mplayer(self.split_device_list(google["media_player"])) gh_volume = self.check_volume(self.get_state(self.gh_select_media_player, attribute="options")) + #self.log("gh_player {}:".format(gh_player)) self.volume_get(gh_volume,float(self.get_state(self.args["gh_restore_volume"]))/100) + self.mediastate_get(gh_volume,float(self.get_state(self.args["gh_restore_volume"]))/100) + #float(self.get_state(globals.get_arg(self.args, "gh_restore_volume")))/100 wait_time = float(self.get_state(self.gh_wait_time)) message = self.replace_regular(google["message_tts"], SUB_TTS) ### set volume @@ -97,6 +116,7 @@ def worker(self): while True: try: data = self.queue.get() + duration = 0 gh_player = self.check_mplayer(self.split_device_list(data["gh_player"])) ### SPEAK if data["gh_mode"].lower() == 'google assistant': @@ -107,15 +127,13 @@ def worker(self): else: entity = gh_player self.call_service(__TTS__ + data["gh_notifier"], entity_id = entity, message = data["text"])#, language = data["language"]) - if type(entity) is list: + if (type(entity) is list) or entity == "all" or \ + (self.get_state(entity, attribute='media_duration') is None) or \ + float(self.get_state(entity, attribute='media_duration')) > 60 or \ + float(self.get_state(entity, attribute='media_duration')) == -1: duration = float(len(data["text"].split())) / 3 + data["wait_time"] else: - if entity == "all": - duration = float(len(data["text"].split())) / 3 + data["wait_time"] - elif self.get_state(entity, attribute='media_duration') is None: - duration = float(len(data["text"].split())) / 3 + data["wait_time"] - else: - duration = self.get_state(entity, attribute='media_duration') + duration = float(self.get_state(entity, attribute='media_duration')) + data["wait_time"] #Sleep and wait for the tts to finish time.sleep(duration) except Exception as ex: @@ -125,12 +143,39 @@ def worker(self): self.queue.task_done() if self.queue.qsize() == 0: + #self.log("QSIZE = 0 - Worker thread exiting") ## RESTORE VOLUME if self.dict_volumes: for i,j in self.dict_volumes.items(): self.call_service("media_player/volume_set", entity_id = i, volume_level = j) # Force Set state self.set_state(i, state="", attributes = {"volume_level": j}) + ## RESTORE MUSIC + if self.dict_info_mplayer: + for k,v in self.dict_info_mplayer.items(): + temp_media_id = '' + temp_media_type = '' + temp_app_name = '' + temp_auth_sig = '' + playing = False + for k1,v1 in v.items(): + if v1 == 'playing': + playing = True + if k1 == 'media_id': + temp_media_id = v1 + if k1 == 'media_type': + temp_media_type = v1 + if k1 == 'app_name': + temp_app_name = v1 + if k1 == 'authSig': + temp_auth_sig = v1 + self.log("Costruzione del servizio: {} - {} - {} - {} - {}".format(k, temp_media_id, temp_media_type, temp_app_name,temp_auth_sig )) + if playing and (temp_auth_sig !=''): + self.call_service("media_player/play_media", entity_id = k, media_content_id = temp_media_id, media_content_type = temp_media_type, authSig = temp_auth_sig) + elif playing and temp_app_name =='Spotify': + self.call_service("spotcast/start", entity_id = k) + elif playing: + self.call_service("media_player/play_media", entity_id = k, media_content_id = temp_media_id, media_content_type = temp_media_type) # It is empty, make callbacks try: while(self._when_tts_done_callback_queue.qsize() > 0): diff --git a/apps/notifier/notification_manager.py b/apps/notifier/notification_manager.py index 1eda290..d25aa53 100644 --- a/apps/notifier/notification_manager.py +++ b/apps/notifier/notification_manager.py @@ -6,87 +6,224 @@ Class Notification_Manager handles sending text to notfyng service """ __NOTIFY__ = "notify/" -SUB_NOTIFICHE = [(" +", " "), ("\s\s+", "\n")] +SUB_NOTIFICHE_NOWRAP = [("\s+"," "),(" +"," ")] +SUB_NOTIFICHE_WRAP = [(" +"," "),("\s\s+","\n")] +SUB_NOTIFIER = [("\s+","_"),("\.","/")] class Notification_Manager(hass.Hass): + def initialize(self): + #self.text_last_message = globals.get_arg(self.args, "text_last_message") self.text_last_message = self.args["text_last_message"] - - def rewrite_notify(self, data, notify_name): - return ( - notify_name - if (str(data).lower() in ["true", "on", "yes"] or data == "1" or data == 1 or data == "") - else data - ) - + self.boolean_wrap_text = self.args["boolean_wrap_text"] + self.boolean_tts_clock = self.args["boolean_tts_clock"] + def prepare_text(self, html, message, title, timestamp, assistant_name): if str(html).lower() in ["true","on","yes","1"]: title = ("[{} - {}] {}".format(assistant_name, timestamp, title)) - title =self.replace_regular(title,[("\s<","<")]) + title = self.replace_regular(title,[("\s<","<")]) else: title = ("*[{} - {}] {}*".format(assistant_name, timestamp, title)) - title =self.replace_regular(title,[("\s\*","*")]) + title = self.replace_regular(title,[("\s\*","*")]) + if self.get_state(self.boolean_wrap_text) == 'on': + message = self.replace_regular(message, SUB_NOTIFICHE_WRAP) + else: + message = self.replace_regular(message, SUB_NOTIFICHE_NOWRAP) return message, title - def send_notify(self, data, notify_name: str, assistant_name: str): + def removekey(self, d, key): + r = dict(d) + del r[key] + return r + + def check_notifier(self, notifier, notify_name): + notifier_list = [] + notify_name_list = [] + notifier_vector = [] + for item in [x.strip(" ") for x in notifier]: + notifier_vector.append(item.lower()) + notifier_list.append(item.lower()) + for item in [x.strip(" ") for x in notify_name]: + notifier_vector.append(item.lower()) + notify_name_list.append(item.lower()) + if any(i in notifier_vector for i in ["1","true","on",1,""]): + notifier_vector.clear() + notifier_vector = notify_name_list + else: + notifier_vector.clear() + notifier_vector = notifier_list + return notifier_vector + + def send_notify(self, data, notify_name, assistant_name: str): timestamp = datetime.datetime.now().strftime("%H:%M:%S") title = data["title"] - message = self.replace_regular(data["message"], SUB_NOTIFICHE) - url = data["url"] - _file = data["file"] + message = data["message"] + image = data["image"] caption = data["caption"] link = data["link"] html = data["html"] - notify_name = self.rewrite_notify(data["notify"], notify_name) - ### SAVE IN INPUT_TEXT.LAST_MESSAGE - self.set_state(self.text_last_message, state=message[:245]) - if notify_name.find("telegram") != -1: - message, title = self.prepare_text(html, message, title, timestamp, assistant_name) - if str(html).lower() not in ["true","on","yes","1"]: - message = message.replace("_", "\_") - if link !="": - message = ("{} {}".format(message,link)) - if caption == "": - caption = "{}\n{}".format(title, message) - if url != "": - extra_data = {"photo": {"url": url, "caption": caption}} - elif _file != "": - extra_data = {"photo": {"file": _file, "caption": caption}} - if url != "" or _file != "": - self.call_service(__NOTIFY__ + notify_name, message="", data=extra_data) + mobile = data["mobile"] + discord = data["discord"] + notify_vector = self.check_notifier(self.split_device_list(str(data["notify"])),self.split_device_list(str(notify_name))) + ########## SAVE IN INPUT_TEXT ########### + self.set_state(self.text_last_message, state = message[:245]) + ######################################### + for item in notify_vector: + if item.find("notify.") == -1: + item = __NOTIFY__ + str(self.replace_regular(item,SUB_NOTIFIER)).lower() else: - self.call_service(__NOTIFY__ + notify_name, message=message, title=title) - elif notify_name.find("whatsapp") != -1: - message, title = self.prepare_text(html, message, title, timestamp, assistant_name) - if link !="": - message = ("{} {}".format(message,link)) - message = title + " " + message - self.call_service(__NOTIFY__ + notify_name, message=message) - else: - if title != "": - title = "[{} - {}] {}".format(assistant_name, timestamp, title) + item = str(self.replace_regular(item,SUB_NOTIFIER)).lower() + #### TELEGRAM ####################### + if item.find("telegram") != -1: + messaggio, titolo = self.prepare_text(html, message, title, timestamp, assistant_name) + extra_data = "" + if str(html).lower() not in ["true","on","yes","1"]: + messaggio = messaggio.replace("_","\_") + if link !="": + messaggio = ("{} {}".format(messaggio,link)) + if caption == "": + caption = ("{}\n{}".format(titolo,messaggio)) + if image != "" and image.find("http") != -1: + extra_data = { "photo": + {"url": image, + "caption": caption, + "timeout": 90} + } + if image != "" and image.find("http") == -1: + extra_data = { "photo": + {"file": image, + "caption": caption, + "timeout": 90} + } + if image != "": + self.call_service(item, message = "", data = extra_data) + else: + self.call_service(item, message = messaggio, title = titolo) + #### WHATSAPP ####################### + elif item.find("whatsapp") != -1: + messaggio, titolo = self.prepare_text(html, message, title, timestamp, assistant_name) + if link !="": + messaggio = ("{} {}".format(messaggio,link)) + messaggio = titolo + " " + messaggio + self.call_service( item, message = messaggio) + #### PUSHOVER ####################### + elif item.find("pushover") != -1: + messaggio, titolo = self.prepare_text(html, message, title, timestamp, assistant_name) + titolo = titolo.replace("*","") + extra_data = {} + if image != "" and image.find("http") != -1: + extra_data.update({"url": image}) + if image != "" and image.find("http") == -1: + extra_data.update({"attachment": image}) + if extra_data: + self.call_service( item, message = messaggio, title = titolo, data = extra_data) + else: + self.call_service( item, message = messaggio, title = titolo) + #### PUSHBULLET ##################### + elif item.find("pushbullet") != -1: + messaggio, titolo = self.prepare_text(html, message, title, timestamp, assistant_name) + titolo = titolo.replace("*","") + extra_data = {} + if link !="": + messaggio = ("{} {}".format(messaggio,link)) + if image != "" and image.find("http") != -1: + extra_data.update( {"url": image}) + if image != "" and image.find("http") == -1: + extra_data.update({"file": image}) + if extra_data: + self.call_service( item, message = messaggio, title = titolo, data = extra_data) + else: + self.call_service( item, message = messaggio, title = titolo) + #### DISCORD ######################## + elif item.find("discord") != -1: + messaggio, titolo = self.prepare_text(html, message, title, timestamp, assistant_name) + extra_data = {} + if isinstance(discord, dict): + if "embed" in discord: + extra_data = discord + extra_data.update({"title":titolo.replace("*","")}) + extra_data.update({"description":messaggio}) + if link !="": + extra_data.update({"url":link}) + elif "images" in discord: + extra_data = discord + messaggio = titolo.replace("*","") + " " + messaggio + else: + messaggio = titolo.replace("*","") + " " + messaggio + # if image != "": + # extra_data.update({"images":image}) + # if extra_data: + # self.call_service( item, message = messaggio, data = extra_data) + #else: + self.call_service( item, message = messaggio) + #### MAIL ########################### + elif item.find("mail") != -1: + messaggio, titolo = self.prepare_text(html, message, title, timestamp, assistant_name) + titolo = titolo.replace("*","") + if link !="": + messaggio = ("{} {}".format(messaggio,link)) + self.call_service( item, message = messaggio, title = titolo) + #### MOBILE ######################### + elif item.find("mobile") != -1: + messaggio, titolo = self.prepare_text(html, message, title, timestamp, assistant_name) + titolo = title + tts_flag = False + extra_data = {} + if isinstance(mobile, dict): + if "tts" in mobile: + if str(mobile.get("tts")).lower() in ["true","on","yes","1"]: + tts_flag = True + extra_data = self.removekey(mobile,"tts") + else: + tts_flag = False + extra_data = self.removekey(mobile,"tts") + else: + extra_data = mobile + if image != "": + extra_data.update({"image":image.replace("config/www","local")}) + if tts_flag: + if self.get_state(self.boolean_tts_clock) == 'on': + titolo = ("{} {}".format(timestamp, titolo + " " + messaggio)) + messaggio = 'TTS' + else: + titolo = ("{}".format(titolo + " " + messaggio)) + messaggio = 'TTS' + else: + titolo = ("[{} - {}] {}".format(assistant_name, timestamp, titolo)) + if link !="": + messaggio = ("{} {}".format(messaggio,link)) + if extra_data: + self.call_service( item, message = messaggio, title = titolo, data = extra_data) + else: + self.call_service( item, message = messaggio, title = titolo) + #### OTHER ######################### else: - title = "[{} - {}]".format(assistant_name, timestamp) - if link !="": - message = ("{} {}".format(message,link)) - self.call_service(__NOTIFY__ + notify_name, message=message, title=title) - + if title != "": + title = "[{} - {}] {}".format(assistant_name, timestamp, title) + else: + title = "[{} - {}]".format(assistant_name, timestamp) + if link !="": + message = ("{} {}".format(message,link)) + self.call_service(item, message=message, title=title) + def send_persistent(self, data, persistent_notification_info): timestamp = datetime.datetime.now().strftime("%H:%M:%S") + messaggio="" try: per_not_info = self.get_state(persistent_notification_info) except: per_not_info = "null" - message = self.replace_regular(data["message"], SUB_NOTIFICHE) - message = "{} - {}".format(timestamp, message) + if self.get_state(self.boolean_wrap_text) == 'on': + messaggio = self.replace_regular(data["message"], SUB_NOTIFICHE_WRAP) + else: + messaggio = self.replace_regular(data["message"], SUB_NOTIFICHE_NOWRAP) + messaggio = ("{} - {}".format(timestamp, messaggio)) if per_not_info == "notifying": - old_message = self.get_state(persistent_notification_info, attribute="message") - message = old_message + "\n" + message if len(old_message) < 2500 else message - self.call_service( - "persistent_notification/create", notification_id="info_messages", message=message, title="Centro Messaggi" - ) + old_messaggio = self.get_state(persistent_notification_info, attribute="message") + messaggio = (old_messaggio + "\n" + messaggio) if len(old_messaggio)<2500 else messaggio + self.call_service("persistent_notification/create", notification_id = "info_messages", message = messaggio, title = "Centro Messaggi" ) def replace_regular(self, text: str, substitutions: list): - for old, new in substitutions: + for old,new in substitutions: text = re.sub(old, new, text.strip()) return text diff --git a/apps/notifier/notification_manager.yaml b/apps/notifier/notification_manager.yaml index c8c6d4e..176495e 100644 --- a/apps/notifier/notification_manager.yaml +++ b/apps/notifier/notification_manager.yaml @@ -3,3 +3,5 @@ Notification_Manager: class: Notification_Manager text_last_message: input_text.last_message + boolean_wrap_text: input_boolean.wrap_text + boolean_tts_clock: input_boolean.tts_clock diff --git a/apps/notifier/notifier_dispatch.py b/apps/notifier/notifier_dispatch.py index 6c6f387..6ec55a1 100644 --- a/apps/notifier/notifier_dispatch.py +++ b/apps/notifier/notifier_dispatch.py @@ -121,8 +121,8 @@ def notify_hub(self, event_name, data, kwargs): guest_status = self.get_state(self.guest_mode) priority_status = (self.get_state(self.priority_message) == "on") or priority_flag ### FROM INPUT SELECT ### - notify_name = self.get_state(self.text_notify).lower().replace(" ", "_") - phone_notify_name = self.get_state(self.phone_notify).lower().replace(" ", "_") + notify_name = self.get_state(self.text_notify) + phone_notify_name = self.get_state(self.phone_notify) ### NOTIFICATION ### if priority_status: useNotification = True diff --git a/apps/notifier/phone_manager.py b/apps/notifier/phone_manager.py index 0599d68..508c514 100644 --- a/apps/notifier/phone_manager.py +++ b/apps/notifier/phone_manager.py @@ -32,6 +32,7 @@ def send_voice_call(self, data, phone_name: str, sip_server_name: str, language: called_number = data["called_number"] lang = self.dict_lingua.get(language) + phone_name = phone_name.lower().replace(" ", "_") if phone_name.find("voip_call") != -1: if called_number != "": called_number = "sip:{}@{}".format(called_number, sip_server_name)