2017-10-04 06:28:44 +00:00
|
|
|
# Copyright 2017 Mycroft AI Inc.
|
2016-05-26 16:16:13 +00:00
|
|
|
#
|
2017-10-04 06:28:44 +00:00
|
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
# you may not use this file except in compliance with the License.
|
|
|
|
# You may obtain a copy of the License at
|
2016-05-26 16:16:13 +00:00
|
|
|
#
|
2017-10-04 06:28:44 +00:00
|
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
2016-05-26 16:16:13 +00:00
|
|
|
#
|
2017-10-04 06:28:44 +00:00
|
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
# See the License for the specific language governing permissions and
|
|
|
|
# limitations under the License.
|
2016-05-26 16:16:13 +00:00
|
|
|
#
|
2016-05-20 14:16:01 +00:00
|
|
|
import json
|
2018-11-16 00:02:12 +00:00
|
|
|
import re
|
2018-01-03 13:26:49 +00:00
|
|
|
from mycroft.util.parse import normalize
|
2016-05-20 14:16:01 +00:00
|
|
|
|
|
|
|
|
|
|
|
class Message(object):
|
2018-01-03 13:26:49 +00:00
|
|
|
"""Holds and manipulates data sent over the websocket
|
|
|
|
|
|
|
|
Message objects will be used to send information back and forth
|
|
|
|
between processes of Mycroft.
|
2017-05-09 22:27:22 +00:00
|
|
|
|
|
|
|
Attributes:
|
2018-01-03 13:26:49 +00:00
|
|
|
type (str): type of data sent within the message.
|
|
|
|
data (dict): data sent within the message
|
2017-05-09 22:27:22 +00:00
|
|
|
context: info about the message not part of data such as source,
|
|
|
|
destination or domain.
|
|
|
|
"""
|
2017-06-30 17:03:24 +00:00
|
|
|
|
2017-11-17 19:07:06 +00:00
|
|
|
def __init__(self, type, data=None, context=None):
|
2017-05-09 18:30:15 +00:00
|
|
|
"""Used to construct a message object
|
|
|
|
|
|
|
|
Message objects will be used to send information back and fourth
|
|
|
|
bettween processes of mycroft service, voice, skill and cli
|
|
|
|
"""
|
2017-11-17 19:07:06 +00:00
|
|
|
data = data or {}
|
2016-09-04 00:59:39 +00:00
|
|
|
self.type = type
|
2016-09-04 01:24:18 +00:00
|
|
|
self.data = data
|
2016-05-20 14:16:01 +00:00
|
|
|
self.context = context
|
|
|
|
|
|
|
|
def serialize(self):
|
2017-05-09 22:27:22 +00:00
|
|
|
"""This returns a string of the message info.
|
|
|
|
|
|
|
|
This makes it easy to send over a websocket. This uses
|
|
|
|
json dumps to generate the string with type, data and context
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
str: a json string representation of the message.
|
|
|
|
"""
|
2016-05-20 14:16:01 +00:00
|
|
|
return json.dumps({
|
2016-09-04 00:59:39 +00:00
|
|
|
'type': self.type,
|
2016-09-04 01:24:18 +00:00
|
|
|
'data': self.data,
|
2016-05-20 14:16:01 +00:00
|
|
|
'context': self.context
|
|
|
|
})
|
|
|
|
|
|
|
|
@staticmethod
|
2016-09-04 01:24:18 +00:00
|
|
|
def deserialize(value):
|
2017-05-09 22:27:22 +00:00
|
|
|
"""This takes a string and constructs a message object.
|
|
|
|
|
|
|
|
This makes it easy to take strings from the websocket and create
|
|
|
|
a message object. This uses json loads to get the info and generate
|
|
|
|
the message object.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
value(str): This is the json string received from the websocket
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
Message: message object constructed from the json string passed
|
|
|
|
int the function.
|
2017-05-09 18:30:15 +00:00
|
|
|
value(str): This is the string received from the websocket
|
|
|
|
"""
|
2016-09-04 01:24:18 +00:00
|
|
|
obj = json.loads(value)
|
|
|
|
return Message(obj.get('type'), obj.get('data'), obj.get('context'))
|
2016-05-20 14:16:01 +00:00
|
|
|
|
2018-06-01 08:23:13 +00:00
|
|
|
def reply(self, type, data=None, context=None):
|
2018-01-03 13:26:49 +00:00
|
|
|
"""Construct a reply message for a given message
|
2017-05-09 22:27:22 +00:00
|
|
|
|
|
|
|
This will take the same parameters as a message object but use
|
2018-01-03 13:26:49 +00:00
|
|
|
the current message object as a reference. It will copy the context
|
|
|
|
from the existing message object and add any context passed in to
|
2017-05-09 22:27:22 +00:00
|
|
|
the function. Check for a target passed in to the function from
|
|
|
|
the data object and add that to the context as a target. If the
|
|
|
|
context has a client name then that will become the target in the
|
|
|
|
context. The new message will then have data passed in plus the
|
|
|
|
new context generated.
|
|
|
|
|
|
|
|
Args:
|
2018-01-03 13:26:49 +00:00
|
|
|
type (str): type of message
|
|
|
|
data (dict): data for message
|
2017-05-09 22:27:22 +00:00
|
|
|
context: intented context for new message
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
Message: Message object to be used on the reply to the message
|
|
|
|
"""
|
2018-06-01 08:23:13 +00:00
|
|
|
data = data or {}
|
2017-11-17 19:07:06 +00:00
|
|
|
context = context or {}
|
2017-06-30 17:03:24 +00:00
|
|
|
|
2016-05-20 14:16:01 +00:00
|
|
|
new_context = self.context if self.context else {}
|
|
|
|
for key in context:
|
|
|
|
new_context[key] = context[key]
|
2016-09-04 01:24:18 +00:00
|
|
|
if 'target' in data:
|
|
|
|
new_context['target'] = data['target']
|
2016-05-20 14:16:01 +00:00
|
|
|
elif 'client_name' in context:
|
|
|
|
context['target'] = context['client_name']
|
2016-09-04 01:24:18 +00:00
|
|
|
return Message(type, data, context=new_context)
|
2016-05-20 14:16:01 +00:00
|
|
|
|
2018-02-15 07:39:11 +00:00
|
|
|
def response(self, data=None, context=None):
|
|
|
|
"""Construct a response message for the message
|
|
|
|
|
|
|
|
Constructs a reply with the data and appends the expected
|
|
|
|
".response" to the message
|
|
|
|
|
|
|
|
Args:
|
|
|
|
data (dict): message data
|
|
|
|
context (dict): message context
|
|
|
|
Returns
|
|
|
|
(Message) message with the type modified to match default response
|
|
|
|
"""
|
|
|
|
response_message = self.reply(self.type, data or {}, context)
|
|
|
|
response_message.type += '.response'
|
|
|
|
return response_message
|
|
|
|
|
2017-11-17 19:07:06 +00:00
|
|
|
def publish(self, type, data, context=None):
|
2017-05-09 22:27:22 +00:00
|
|
|
"""
|
|
|
|
Copy the original context and add passed in context. Delete
|
|
|
|
any target in the new context. Return a new message object with
|
|
|
|
passed in data and new context. Type remains unchanged.
|
|
|
|
|
|
|
|
Args:
|
2018-01-03 13:26:49 +00:00
|
|
|
type (str): type of message
|
|
|
|
data (dict): date to send with message
|
2017-05-09 22:27:22 +00:00
|
|
|
context: context added to existing context
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
Message: Message object to publish
|
|
|
|
"""
|
2017-11-17 19:07:06 +00:00
|
|
|
context = context or {}
|
2016-05-20 14:16:01 +00:00
|
|
|
new_context = self.context.copy() if self.context else {}
|
|
|
|
for key in context:
|
|
|
|
new_context[key] = context[key]
|
|
|
|
|
|
|
|
if 'target' in new_context:
|
|
|
|
del new_context['target']
|
|
|
|
|
2016-09-04 01:24:18 +00:00
|
|
|
return Message(type, data, context=new_context)
|
2018-01-03 13:26:49 +00:00
|
|
|
|
|
|
|
def utterance_remainder(self):
|
|
|
|
"""
|
|
|
|
For intents get the portion not consumed by Adapt.
|
|
|
|
|
|
|
|
For example: if they say 'Turn on the family room light' and there are
|
|
|
|
entity matches for "turn on" and "light", then it will leave behind
|
|
|
|
" the family room " which is then normalized to "family room".
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
str: Leftover words or None if not an utterance.
|
|
|
|
"""
|
2018-03-24 22:58:00 +00:00
|
|
|
utt = normalize(self.data.get("utterance", ""))
|
2018-01-03 13:26:49 +00:00
|
|
|
if utt and "__tags__" in self.data:
|
|
|
|
for token in self.data["__tags__"]:
|
2018-11-16 00:02:12 +00:00
|
|
|
# Substitute only whole words matching the token
|
|
|
|
utt = re.sub(r'\b' + token.get("key", "") + r"\b", "", utt)
|
2018-01-03 13:26:49 +00:00
|
|
|
return normalize(utt)
|