From f6deb610b18b98b6dbac22a4eb90813739b191ee Mon Sep 17 00:00:00 2001 From: Philippe Dionne Date: Fri, 20 Jan 2017 15:51:13 -0500 Subject: [PATCH 1/2] Add alexa support --- Readme.md | 2 +- lib/alexa/incoming.js | 40 ++++++++++++++++++++++++++++++++++++++++ lib/alexa/index.js | 22 ++++++++++++++++++++++ lib/alexa/outgoing.js | 28 ++++++++++++++++++++++++++++ 4 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 lib/alexa/incoming.js create mode 100644 lib/alexa/index.js create mode 100644 lib/alexa/outgoing.js diff --git a/Readme.md b/Readme.md index c5a8dec..083a531 100644 --- a/Readme.md +++ b/Readme.md @@ -7,7 +7,7 @@ A node.js client for the [Dialog](https://dialoganalytics.com) API. ## Examples -- Amazon Alexa (soon) +- [Amazon Alexa](https://github.com/dialoganalytics/alexa-node-example) - Google Actions (soon) - [Facebook Messenger with Botkit](https://github.com/dialoganalytics/botkit-messenger-example) - [Twilio Programmable Chat with Botkit](https://github.com/dialoganalytics/botkit-twilio-ipm-example) diff --git a/lib/alexa/incoming.js b/lib/alexa/incoming.js new file mode 100644 index 0000000..a745936 --- /dev/null +++ b/lib/alexa/incoming.js @@ -0,0 +1,40 @@ +'use strict'; + +{ + "session": { + "sessionId": "SessionId.5531df55-fed9-4a8e-9217-afb2b1deb955", + "application": { + "applicationId": "amzn1.ask.skill.1b83076b-8262-4da4-a773-43a58ee9a821" + }, + "attributes": {}, + "user": { + "userId": "amzn1.ask.account.AFLTEFO4OZJWRSSB5QRZ7GPQAGY72ZW4CIGRDXMWUXAUN6SYTTX2GRD4GER6GJ3GNGTFSOEDP56MJMFPQCYIGPSCTQHA33ZOKQWDDU2TXFOPM5F3WFEG5SBJXEZYN7K45UH63NXC65C77KAW4RX52OR7ESPNBYQF4SEEB4CVEQH6FR4YVNMIMS5PW4IVTO245XKD7VF2M4UHVQY" + }, + "new": true + }, + "request": { + "type": "IntentRequest", + "requestId": "EdwRequestId.03f17ea3-e95f-43a5-96f9-428cca0cdd87", + "locale": "en-US", + "timestamp": "2017-01-20T16:47:18Z", + "intent": { + "name": "GetName", + "slots": { + "Name": { + "name": "Name", + "value": "Nick" + } + } + } + }, + "version": "1.0" +} + +// @param {Object} client +// @param {Object} data Payload received from Alexa +// @param {Function} callback +function incoming(that.client, payload, callback) { + +}; + +module.exports = incoming; diff --git a/lib/alexa/index.js b/lib/alexa/index.js new file mode 100644 index 0000000..9e62b9b --- /dev/null +++ b/lib/alexa/index.js @@ -0,0 +1,22 @@ +'use strict'; + +var Client = require("../client"); + +var incomingHandler = require("./incoming"); +var outgoingHandler = require("./outgoing"); + +module.exports = function(apiToken, botId) { + var that = this; + + this.client = new Client(apiToken, botId); + + this.incoming = function(payload, callback) { + incomingHandler(that.client, payload, callback) + }; + + this.outgoing = function(payload, response, callback) { + outgoingHandler(that.client, payload, response, callback) + }; + + return this; +}; diff --git a/lib/alexa/outgoing.js b/lib/alexa/outgoing.js new file mode 100644 index 0000000..2ceb95a --- /dev/null +++ b/lib/alexa/outgoing.js @@ -0,0 +1,28 @@ +'use strict'; + +{ + "response": { + "outputSpeech": { + "type": "PlainText", + "text": "I think Nick is a good old pal" + }, + "card": { + "content": "I think Nick is a good old pal", + "title": "Dialog", + "type": "Simple" + }, + "shouldEndSession": false + }, + "sessionAttributes": {} +} + +// @param {Object} client +// @param {Object} data Payload sent to Alexa API +// @param {Object} event Response from Alexa API +// @param {Function} callback + +function outgoing(that.client, payload, response, callback) { + +}; + +module.exports = outgoing; From 963de450161e9696ff44a1feded8f267ab0623b1 Mon Sep 17 00:00:00 2001 From: Philippe Dionne Date: Fri, 27 Jan 2017 12:32:16 -0500 Subject: [PATCH 2/2] WIP --- lib/alexa/incoming.js | 82 +++++++++++++++++++++++++++++-------------- lib/alexa/index.js | 77 ++++++++++++++++++++++++++++++++++++++++ lib/alexa/locale.js | 10 ++++++ lib/alexa/outgoing.js | 30 ++++++++++++++-- 4 files changed, 170 insertions(+), 29 deletions(-) create mode 100644 lib/alexa/locale.js diff --git a/lib/alexa/incoming.js b/lib/alexa/incoming.js index a745936..252a87f 100644 --- a/lib/alexa/incoming.js +++ b/lib/alexa/incoming.js @@ -1,40 +1,68 @@ 'use strict'; -{ - "session": { - "sessionId": "SessionId.5531df55-fed9-4a8e-9217-afb2b1deb955", - "application": { - "applicationId": "amzn1.ask.skill.1b83076b-8262-4da4-a773-43a58ee9a821" +var locale = require('./locale') + +// Incoming payload for Dialog API +// +// { +// "session": { +// "sessionId": "SessionId.5531df55-fed9-4a8e-9217-afb2b1deb955", +// "application": { +// "applicationId": "amzn1.ask.skill.1b83076b-8262-4da4-a773-43a58ee9a821" +// }, +// "attributes": {}, +// "user": { +// "userId": "amzn1.ask.account.AFLTEFO4OZJWRSSB5QRZ7GPQAGY72ZW4CIGRDXMWUXAUN6SYTTX2GRD4GER6GJ3GNGTFSOEDP56MJMFPQCYIGPSCTQHA33ZOKQWDDU2TXFOPM5F3WFEG5SBJXEZYN7K45UH63NXC65C77KAW4RX52OR7ESPNBYQF4SEEB4CVEQH6FR4YVNMIMS5PW4IVTO245XKD7VF2M4UHVQY" +// }, +// "new": true +// }, +// "request": { +// "type": "IntentRequest", +// "requestId": "EdwRequestId.03f17ea3-e95f-43a5-96f9-428cca0cdd87", +// "locale": "en-US", +// "timestamp": "2017-01-20T16:47:18Z", +// "intent": { +// "name": "GetName", +// "slots": { +// "Name": { +// "name": "Name", +// "value": "Nick" +// } +// } +// } +// }, +// "version": "1.0" +// } +// +// @param {Object} payload +function incomingPayload(payload) { + return { + message: { + distinct_id: payload.request.requestId, + platform: 'alexa', + provider: 'dialog-node', + mtype: 'text', + sent_at: Date.parse(payload.request.timestamp) / 1000, + properties: { + text: "TODO" + } }, - "attributes": {}, - "user": { - "userId": "amzn1.ask.account.AFLTEFO4OZJWRSSB5QRZ7GPQAGY72ZW4CIGRDXMWUXAUN6SYTTX2GRD4GER6GJ3GNGTFSOEDP56MJMFPQCYIGPSCTQHA33ZOKQWDDU2TXFOPM5F3WFEG5SBJXEZYN7K45UH63NXC65C77KAW4RX52OR7ESPNBYQF4SEEB4CVEQH6FR4YVNMIMS5PW4IVTO245XKD7VF2M4UHVQY" + conversation: { + distinct_id: payload.session.sessionId }, - "new": true - }, - "request": { - "type": "IntentRequest", - "requestId": "EdwRequestId.03f17ea3-e95f-43a5-96f9-428cca0cdd87", - "locale": "en-US", - "timestamp": "2017-01-20T16:47:18Z", - "intent": { - "name": "GetName", - "slots": { - "Name": { - "name": "Name", - "value": "Nick" - } - } + creator: { + distinct_id: payload.user.userId, + type: 'interlocutor', + locale: locale(payload.request.locale) } - }, - "version": "1.0" + }; } // @param {Object} client -// @param {Object} data Payload received from Alexa +// @param {Object} payload Payload received from Alexa // @param {Function} callback function incoming(that.client, payload, callback) { - + return client.track(incomingPayload(payload), callback); }; module.exports = incoming; diff --git a/lib/alexa/index.js b/lib/alexa/index.js index 9e62b9b..719debe 100644 --- a/lib/alexa/index.js +++ b/lib/alexa/index.js @@ -20,3 +20,80 @@ module.exports = function(apiToken, botId) { return this; }; + +// Valid event types +// INITIALIZE +// INSTALL +// SESSION_START +// SPEECH +// SESSION_END + +/** +* Fires the track event and executes the callback +* function with the speech output SSML + response +* @param {String} intentName The intent string +* @param {Object} intentMetadata Object containing intent metadata like slots +* @param {String} speechText String speech out by Alexa +* @param {Function} cb Callback function for success +* @return {Void} +*/ +track: function(intentName, intentMetadata, speechText, cb){ + var error = false; + + //Error check to make sure we have everything we need + if(!config.app_token || !config.app_token.length || !_.isString(config.app_token)){ + console.error(errors.noTokenError); + error = true; + } + + if(!config.session_id){ + console.error(errors.invalidSessionId); + error = true; + } + + if(!config.user_hashed_id){ + console.error(errors.invalidUserError); + error = true; + } + + if(!intentName) { + console.error(errors.invalidIntentNameError); + error = true; + } + + if(error){ + if(_.isFunction(cb)){ + cb({ + error: 'Invalid arguments passed to track(). See standard error for details.' + }); + } + + return; + } + + var payload = { + app_token: config.app_token, + user_hashed_id: config.user_hashed_id, + session_id: config.session_id, + intent: intentName, + data: { + metadata: intentMetadata, + speech: speechText, + }, + }; + + // check if session exists + if(!initialized){ + initialized = true; + config.agent = 'alexa'; + + // trigger session event + trigger(Enums.eventTypes.INITIALIZE, config, function(){ + // trigger speech event + trigger(Enums.eventTypes.SPEECH, payload, cb); + }); + }else{ + // trigger speech event + trigger(Enums.eventTypes.SPEECH, payload, cb); + } +} diff --git a/lib/alexa/locale.js b/lib/alexa/locale.js new file mode 100644 index 0000000..d5ad32e --- /dev/null +++ b/lib/alexa/locale.js @@ -0,0 +1,10 @@ +'use strict'; + +// Converts a Amazon locale into ISO 639-1 format +// @param {String} Amazon locale formated like 'en-US' +// @returns {String} ISO 639-1 locale. Example: 'en' +function locale(locale) { + return locale.split('-')[0]; +}; + +module.exports = locale; diff --git a/lib/alexa/outgoing.js b/lib/alexa/outgoing.js index 2ceb95a..19506aa 100644 --- a/lib/alexa/outgoing.js +++ b/lib/alexa/outgoing.js @@ -1,7 +1,12 @@ 'use strict'; { + "version": "1.0", "response": { + // "outputSpeech": { + // "type": "SSML", + // "ssml": " Hello World! " + // }, "outputSpeech": { "type": "PlainText", "text": "I think Nick is a good old pal" @@ -16,13 +21,34 @@ "sessionAttributes": {} } +function outgoingPayload(payload) { + return { + message: { + distinct_id: , + provider: , + platform: , + mtype: 'text', + sent_at: , + properties: { + + } + }, + conversation: { + distinct_id: + }, + creator: { + distinct_id: '', + type: 'bot' + } + }; +} + // @param {Object} client // @param {Object} data Payload sent to Alexa API // @param {Object} event Response from Alexa API // @param {Function} callback - function outgoing(that.client, payload, response, callback) { - + return client.track(outgoingPayload(payload), callback); }; module.exports = outgoing;