def update_profile(self, new_preferences: dict, message: Message = None): """ Updates a user profile with the passed new_preferences :param new_preferences: dict of updated preference values. Should follow {section: {key: val}} format :param message: Message associated with request """ if self.server: nick = get_message_user(message) if message else None new_skills_prefs = new_preferences.pop("skills") old_skills_prefs = message.context["nick_profiles"][nick]["skills"] combined_skill_prefs = {**old_skills_prefs, **new_skills_prefs} combined_changes = {k: v for dic in new_preferences.values() for k, v in dic.items()} if new_skills_prefs: combined_changes["skill_settings"] = json.dumps(list(combined_skill_prefs.values())) new_preferences["skills"] = combined_skill_prefs LOG.debug(f"combined_skill_prefs={combined_skill_prefs}") combined_changes["username"] = nick self.socket_emit_to_server("update profile", ["skill", combined_changes, message.context["klat_data"]["request_id"]]) self.bus.emit(Message("neon.remove_cache_entry", {"nick": nick})) old_preferences = message.context["nick_profiles"][nick] message.context["nick_profiles"][nick] = {**old_preferences, **new_preferences} else: for section, settings in new_preferences: # section in user, brands, units, etc. for key, val in settings: self.user_config[section][key] = val self.user_config.write_changes()
def send_email(self, title, body, message=None, email_addr=None, attachments=None): """ Send an email to the registered user's email. Email address priority: email_addr, user prefs from message, fallback to DeviceApi for Mycroft method Arguments: title (str): Title of email body (str): HTML body of email. This supports simple HTML like bold and italics email_addr (str): Optional email address to use attachments (dict): Optional dict of file names to Base64 encoded files message (Message): Optional message to get email from """ if not email_addr and message: email_addr = self.preference_user(message).get("email") if email_addr: LOG.info("Send email via Neon Server") try: LOG.debug(f"body={body}") self.bus.emit(Message("neon.send_email", {"title": title, "email": email_addr, "body": body, "attachments": attachments})) except Exception as e: LOG.error(e) else: super().send_email(title, body)
def check_state(self): """Check if an event should be triggered.""" with self.event_lock: # Check all events pending_messages = [] for event in self.events: current_time = time.time() e = self.events[event] # Get scheduled times that has passed passed = [(t, r, d) for (t, r, d) in e if t <= current_time] # and remaining times that we're still waiting for remaining = [(t, r, d) for t, r, d in e if t > current_time] # Trigger registered methods for sched_time, repeat, data in passed: pending_messages.append(Message(event, data)) # if this is a repeated event add a new trigger time if repeat: next_time = repeat_time(sched_time, repeat) remaining.append((next_time, repeat, data)) # update list of events self.events[event] = remaining # Remove events have are now completed self.clear_empty() # Finally, emit the queued up events that triggered for msg in pending_messages: self.bus.emit(msg)
def make_active(self, duration_minutes=5): """Bump skill to active_skill list in intent_service. This enables converse method to be called even without skill being used in last 5 minutes. :param duration_minutes: duration in minutes for skill to remain active (-1 for infinite) """ self.bus.emit(Message("active_skill_request", {"skill_id": self.skill_id, "timeout": duration_minutes}))
def report_metric(self, name, data): """Report a skill metric to the Mycroft servers. Arguments: name (str): Name of metric. Must use only letters and hyphens data (dict): JSON dictionary to report. Must be valid JSON """ combinded = deepcopy(data) combinded["name"] = name self.bus.emit(Message("neon.metric", combinded))
def request_check_timeout(self, time_wait, intent_to_check): LOG.info("request received") LOG.info(time_wait) LOG.info(len(intent_to_check)) try: if isinstance(intent_to_check, str): intent_to_check = [intent_to_check] for intent in intent_to_check: data = {'time_out': time_wait, 'intent_to_check': f"{self.skill_id}:{intent}"} LOG.debug(f"DM: Set Timeout: {data}") self.bus.emit(Message("set_timeout", data)) except Exception as x: LOG.error(x)
def mobile_skill_intent(self, action: str, arguments: dict, message: Message): """ Handle a mobile skill intent response :param action: Name of action or event for mobile device to handle :param arguments: dict of key/value arguments to pass with action :param message: Message associated with request """ fmt_args = "" for key, value in arguments: fmt_args += f"&{key}={value}" if self.server: emit_data = [action, fmt_args, message.context["klat_data"]["request_id"]] self.bus.emit(Message("css.emit", {"event": "mobile skill intent", "data": emit_data})) else: LOG.warning("Mobile intents are not supported on this device yet.")
def on_message(self, _, message): parsed_message = Message.deserialize(message) self.emitter.emit('message', message) self.emitter.emit(parsed_message.msg_type, parsed_message)
def speak(self, utterance, expect_response=False, wait=False, meta=None, message=None, private=False, speaker=None): """ Speak a sentence. Arguments: utterance (str): sentence mycroft should speak expect_response (bool): set to True if Mycroft should listen for a response immediately after speaking the utterance. message (Message): message associated with the input that this speak is associated with private (bool): flag to indicate this message contains data that is private to the requesting user speaker (dict): dict containing language or voice data to override user preference values wait (bool): set to True to block while the text is being spoken. meta: Information of what built the sentence. """ # registers the skill as being active meta = meta or {} meta['skill'] = self.name self.enclosure.register(self.name) if utterance: if not message: # Find the associated message LOG.debug('message is None.') message = dig_for_message() if not message: message = Message("speak") if not speaker: speaker = message.data.get("speaker", None) nick = get_message_user(message) if private and self.server: LOG.debug("Private Message") title = message.context["klat_data"]["title"] need_at_sign = True if title.startswith("!PRIVATE"): users = title.split(':')[1].split(',') for idx, val in enumerate(users): users[idx] = val.strip() if len(users) == 2 and "Neon" in users: need_at_sign = False elif len(users) == 1: need_at_sign = False elif nick.startswith("guest"): need_at_sign = False if need_at_sign: LOG.debug("Send message to private cid!") utterance = f"@{nick} {utterance}" data = {"utterance": utterance, "expect_response": expect_response, "meta": meta, "speaker": speaker} if message.context.get("cc_data", {}).get("emit_response"): msg_to_emit = message.reply("skills:execute.response", data) else: message.context.get("timing", {})["speech_start"] = time.time() msg_to_emit = message.reply("speak", data, message.context) LOG.debug(f"Skill speak! {data}") LOG.debug(msg_to_emit.msg_type) self.bus.emit(msg_to_emit) else: LOG.warning("Null utterance passed to speak") LOG.warning(f"{self.name} | message={message}") if wait: wait_while_speaking()
def socket_emit_to_server(self, event: str, data: list): LOG.debug(f"Emit event={event}, data={data}") self.bus.emit(Message("css.emit", {"event": event, "data": data}))