"""Blackout Nexus hook. Base class for all hooks"""
# System imports
import os
import base64
import json
import sys
import inspect
if sys.version_info.major == 2:
from urlparse import urlsplit
else:
from urllib.parse import urlsplit
# 3rd Party imports
from btNode import Node # have it like this so it will still be possible to seperate it into its own package
# local imports
from nexus.btNexusMemory import BTNexusMemory
from nexus.btNexusData import BTNexusData
# end file header
__author__ = "Adrian Lubitz"
__copyright__ = "Copyright (c)2017, Blackout Technologies"
[docs]class Hook(Node):
"""
Blackout Nexus hook. Base class for all hooks
"""
def __init__(self, connectHash = None, **kwargs):
"""
Constructor for the hook.
extracting all important infos from the connectHash
(either given as parameter, via environment variable CONNECT_HASH or in the .btnexusrc(prioritized in this order))
"""
configpath = os.path.join(os.path.dirname(os.path.realpath(os.path.abspath(inspect.getfile(self.__class__)))), 'package.json')
captionsPath = os.path.join(os.path.dirname(os.path.realpath(os.path.abspath(inspect.getfile(self.__class__)))), 'captions.json')
with open(configpath) as jsonFile:
self.version = json.load(jsonFile)["version"]
try:
with open(captionsPath) as jsonFile:
self.captions = json.load(jsonFile)
except:
print("Couldn't load captions")
#get connectHash
self.initKwargs = kwargs
if connectHash == None:
if "CONNECT_HASH" in os.environ:
connectHash = os.environ["CONNECT_HASH"]
else:
with open(".btnexusrc") as btnexusrc:
connectHash = btnexusrc.read()
#extract config
self.config = json.loads(base64.b64decode(connectHash))
#call super constructor with axon and token set
self.token = self.config["token"]
self.host = urlsplit(self.config["host"]).netloc
if not self.host:
self.host = self.config["host"] # backwardscompatibility
self.memory = BTNexusMemory("https://" + self.host, self.token)
self.data = BTNexusData("https://" + self.host, self.token, self.config['id'])
super(Hook, self).__init__(self.token, self.host)
self.onInit(**kwargs)
self.connect()
[docs] def onConnected(self):
"""
Setup all Callbacks
"""
self.memory.addEvent(self.memoryData)
# Join complete
self.subscribe(self.config["id"], 'hookChat', self._onMessage, "onMessage")
self.subscribe(self.config["id"], "state", self.state)
self.readyState = "ready"
self.state()
self.onReady(**self.initKwargs)
[docs] def state(self):
"""
publish the state of the hook
"""
self.publish(self.config["id"], self.config["id"], 'state', {
'hookId': self.config["id"],
'state': self.readyState
})
def _onMessage(self, **kwargs):
"""
Forwards the correct params to onMessage.
This method is just for internal use.
"""
self.onMessage(originalTxt=kwargs["text"]["original"],
intent=kwargs["intent"],
language=kwargs["language"],
entities=kwargs["entities"],
slots=kwargs["slots"],
branchName=kwargs["branch"]["name"],
peer=kwargs)#TODO: what is needed peer? only needs infos to indentify message origin
[docs] def onMessage(self, originalTxt, intent, language, entities, slots, branchName, peer):
"""
Overload for your custum hook! - it needs to trigger say
React on a message forwarded to the hook.
:param originalTxt: the original text
:type originalTxt: String
:param intent: the classified intent
:type intent: String
:param language: the (classified) language
:type language: String
:param entities: List of used entities
:type entities: List of (String)
:param slots: List of used slots
:type slots: List of (String)
:param branchName: Name of the Branch
:type branchName: String
:param peer: param to indentify message origin
:type peer: dict
"""
self.say(peer, {'answer':"Hook needs to overload onMessage"}) # if not overloaded this is what your hook will say
[docs] def say(self, peer, message): # TODO: check if message is just a string wrap it in the answer field ;)
"""
publishes the hooks response.
:param message: the message dict with at least the field 'answer'
:type message: dict
:param peer: the peer object handed from onMessage
:type peer: Object
"""
peer["message"] = message
self.publish(peer["personalityId"], 'chat', 'hookResponse', peer)
[docs] def onReady(self, **kwargs):
"""
Initilize what you need after the hook connected - you can pass kwargs in the constructor to use them here
"""
if kwargs:
print("onReady with params: {}".format(kwargs))
[docs] def onInit(self, **kwargs):
"""
Initilize what you need before the hook connected - you can pass kwargs in the constructor to use them here
"""
if kwargs:
print("onInit with params: {}".format(kwargs))
[docs] def setUp(self):
"""
Register the hook in the system
"""
self.memoryData = {
'service': "hook",
'context': self.config['id'],
'version': self.version #TODO: this should be the version of the implementation not the lib
}
[docs] def cleanUp(self):
"""
Unregister the hook and send exit state
"""
self.memory.removeEvent(self.memoryData)
self.readyState = 'exit'
self.state()
[docs] def save(self, key, value, callback=None):
"""
save a value to a specific key in the NexusData Api
:param key: the key to which the value should be saved
:type key: String
:param value: the object which should be saved
:type value: Object
:param callback: callback to handle the api response
:type callback: function pointer
"""
self.data.save(key, value, callback)
[docs] def load(self, key, callback=None):
"""
load a value to a specific key in the NexusData Api
:param key: the key to which the value should be saved
:type key: String
:param callback: callback to handle the api response
:type callback: function pointer
"""
self.data.load(key, callback)
[docs] def put(self, key, value, callback=None):
"""
add a value to a specific key in the NexusData Api - the value must be a list otherwise it will be overwritten
:param key: the key to which the value should be saved
:type key: String
:param value: the object which should be saved
:type value: Object
:param callback: callback to handle the api response
:type callback: function pointer
"""
self.data.put(key, value, callback)
if __name__ == "__main__":
h = Hook(test="TestParam")