Source code for platypush.message.event.assistant

import re
import sys
from typing import Any, Mapping, Optional, Union

from platypush.context import get_plugin
from platypush.message.event import Event, EventMatchResult
from platypush.plugins.assistant import AssistantPlugin
from platypush.utils import get_plugin_name_by_class


[docs] class AssistantEvent(Event): """Base class for assistant events"""
[docs] def __init__( self, *args, assistant: Optional[Union[str, AssistantPlugin]] = None, **kwargs ): """ :param assistant: Name of the assistant plugin that triggered the event. """ assistant = assistant or kwargs.get('assistant') if assistant: kwargs['plugin'] = kwargs['_assistant'] = ( assistant if isinstance(assistant, str) else get_plugin_name_by_class(assistant.__class__) ) super().__init__(*args, **kwargs)
@property def assistant(self) -> Optional[AssistantPlugin]: assistant = self.args.get('_assistant') if not assistant: return None return get_plugin(assistant)
[docs] def as_dict(self): evt_dict = super().as_dict() evt_args = {**evt_dict['args']} assistant = evt_args.pop('_assistant', None) if assistant: evt_args['assistant'] = assistant return { **evt_dict, 'args': evt_args, }
[docs] class ConversationStartEvent(AssistantEvent): """ Event triggered when a new conversation starts """
[docs] def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs)
[docs] class ConversationEndEvent(AssistantEvent): """ Event triggered when a conversation ends """
[docs] def __init__(self, *args, with_follow_on_turn: bool = False, **kwargs): """ :param with_follow_on_turn: Set to true if the conversation expects a user follow-up, false otherwise """ super().__init__(*args, with_follow_on_turn=with_follow_on_turn, **kwargs)
[docs] class ConversationTimeoutEvent(ConversationEndEvent): """ Event triggered when a conversation times out """
[docs] def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs)
[docs] class ResponseEvent(AssistantEvent): """ Event triggered when a response is processed by the assistant """
[docs] def __init__( self, *args, response_text: str, with_follow_on_turn: bool = False, **kwargs ): """ :param response_text: Response text processed by the assistant :param with_follow_on_turn: Set to true if the conversation expects a user follow-up, false otherwise """ super().__init__( *args, response_text=response_text, with_follow_on_turn=with_follow_on_turn, **kwargs, )
[docs] class ResponseEndEvent(ConversationEndEvent): """ Event triggered when a response has been rendered on the assistant. """
[docs] def __init__( self, *args, response_text: str, with_follow_on_turn: bool = False, **kwargs ): """ :param response_text: Response text rendered on the assistant. :param with_follow_on_turn: Set to true if the conversation expects a user follow-up, false otherwise. """ super().__init__( *args, response_text=response_text, with_follow_on_turn=with_follow_on_turn, **kwargs, )
[docs] class NoResponseEvent(ConversationEndEvent): """ Event triggered when a conversation ends with no response """
[docs] class SpeechRecognizedEvent(AssistantEvent): """ Event triggered when a speech is recognized """
[docs] def __init__(self, *args, phrase: str, **kwargs): """ :param phrase: Recognized user phrase """ super().__init__(*args, phrase=phrase, **kwargs) self.recognized_phrase = phrase.strip().lower()
[docs] def matches_condition(self, condition): """ Overrides matches condition, and stops the conversation to prevent the default assistant response if the event matched some event hook condition. """ result = super().matches_condition(condition) if ( result.is_match and condition.args.get('phrase') and self.assistant and self.assistant.stop_conversation_on_speech_match ): self.assistant.stop_conversation() return result
def _matches_argument(self, argname, condition_value, event_args, result): """ Overrides the default `_matches_argument` method to allow partial phrase matches and text extraction. Example:: event_args = { 'phrase': 'Hey dude turn on the living room lights' } - `self._matches_argument(argname='phrase', condition_value='Turn on the ${lights} lights')` will return `EventMatchResult(is_match=True, parsed_args={ 'lights': 'living room' })` - `self._matches_argument(argname='phrase', condition_value='Turn off the ${lights} lights')` will return `EventMatchResult(is_match=False, parsed_args={})` """ if event_args.get(argname) == condition_value: # In case of an exact match, return immediately result.is_match = True result.score = sys.maxsize return result parsed_args = {} event_tokens = re.split(r'\s+', event_args.get(argname, '').strip().lower()) condition_tokens = re.split(r'\s+', condition_value.strip().lower()) while event_tokens and condition_tokens: event_token = event_tokens[0] condition_token = condition_tokens[0] if event_token == condition_token: event_tokens.pop(0) condition_tokens.pop(0) result.score += 1.5 elif re.search(condition_token, event_token): m = re.search(f'({condition_token})', event_token) if m and m.group(1): event_tokens.pop(0) result.score += 1.25 condition_tokens.pop(0) else: m = re.match(r'[^\\]*\${(.+?)}', condition_token) if m: argname = m.group(1) if argname not in parsed_args: parsed_args[argname] = event_token result.score += 1.0 else: parsed_args[argname] += ' ' + event_token if (len(condition_tokens) == 1 and len(event_tokens) == 1) or ( len(event_tokens) > 1 and len(condition_tokens) > 1 and event_tokens[1] == condition_tokens[1] ): # Stop appending tokens to this argument, as the next # condition will be satisfied as well condition_tokens.pop(0) event_tokens.pop(0) else: result.score -= 1.0 event_tokens.pop(0) # It's a match if all the tokens in the condition string have been satisfied result.is_match = len(condition_tokens) == 0 if result.is_match: result.parsed_args = parsed_args return result
[docs] class IntentRecognizedEvent(AssistantEvent): """ Event triggered when an intent is matched by a speech command. """
[docs] def __init__( self, *args, intent: str, slots: Optional[Mapping[str, Any]] = None, **kwargs ): """ :param intent: The intent that has been matched. :param slots: The slots extracted from the intent, as a key-value mapping. """ super().__init__(*args, intent=intent, slots=slots or {}, **kwargs)
[docs] def matches_condition(self, condition): """ Overrides matches condition, and stops the conversation to prevent the default assistant response if the event matched some event hook condition. """ result = super().matches_condition(condition) if ( result.is_match and self.assistant and self.assistant.stop_conversation_on_speech_match ): self.assistant.stop_conversation() return result
def _matches_argument( self, argname, condition_value, event_args, result: EventMatchResult ): if argname != 'slots': return super()._matches_argument( argname, condition_value, event_args, result ) event_slots = set(event_args.get(argname, {}).items()) slots = set(self.args.get(argname, {}).items()) # All the slots in the condition must be in the event if slots.difference(event_slots) == 0: result.is_match = True result.score += 1 else: result.is_match = False result.score = 0 return result
[docs] class HotwordDetectedEvent(AssistantEvent): """ Event triggered when a custom hotword is detected """
[docs] def __init__(self, *args, hotword: Optional[str] = None, **kwargs): """ :param hotword: The detected user hotword. """ super().__init__(*args, hotword=hotword, **kwargs)
[docs] class VolumeChangedEvent(AssistantEvent): """ Event triggered when the volume of the assistant changes. """
[docs] def __init__(self, *args, volume: float, **kwargs): super().__init__(*args, volume=volume, **kwargs)
[docs] class AlertStartedEvent(AssistantEvent): """ Event triggered when an alert starts on the assistant """
[docs] class AlertEndEvent(AssistantEvent): """ Event triggered when an alert ends on the assistant """
[docs] class AlarmStartedEvent(AlertStartedEvent): """ Event triggered when an alarm starts on the assistant """
[docs] class AlarmEndEvent(AlertEndEvent): """ Event triggered when an alarm ends on the assistant """
[docs] class TimerStartedEvent(AlertStartedEvent): """ Event triggered when a timer starts on the assistant """
[docs] class TimerEndEvent(AlertEndEvent): """ Event triggered when a timer ends on the assistant """
[docs] class MicMutedEvent(AssistantEvent): """ Event triggered when the microphone is muted. """
[docs] class MicUnmutedEvent(AssistantEvent): """ Event triggered when the microphone is muted. """
# vim:sw=4:ts=4:et: