Last active
October 19, 2018 18:48
-
-
Save DanLaufer/75e780b2fabb747bbc1a4fb01a588e06 to your computer and use it in GitHub Desktop.
Alexa Skill - Example including relevant JS files
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const languageStrings = require('./languageStrings'); | |
const getSession = require('./util').getSession; // getSession(handlerInput) | |
const setSession = require('./util').setSession; // setSession(handerInput, attributes) | |
const buildResponse = require('./util').buildResponse; // buildResponse(handlerInput, speakText, repromptText = null) | |
const buildFinalResponse = require('./util').buildFinalResponse; // buildFinalResponse(handlerInput, speakText) | |
const checkIntentType = require('./util').checkIntentType; | |
const checkIntentName = require('./util').checkIntentName; | |
const CancelAndStopIntentHandler = { | |
canHandle : function(handlerInput) { | |
return checkIntentType(handlerInput, 'IntentRequest') | |
&& (checkIntentName(handlerInput, 'AMAZON.CancelIntent') | |
|| checkIntentName(handlerInput, 'AMAZON.StopIntent')); | |
}, | |
handle : function(handlerInput) { | |
const speechText = languageStrings['GOODBYE']; | |
return buildFinalResponse(handlerInput, speechText); | |
} | |
}; | |
const GetCurrentStepHandler = { | |
canHandle : function(handlerInput) { | |
return checkIntentType(handlerInput, 'IntentRequest') | |
&& checkIntentName(handlerInput, 'GetCurrentStep'); | |
}, | |
handle : function(handlerInput) { | |
let speechText = ''; | |
let session_attributes = getSession(handlerInput); | |
// if they replied "no" to "would you like to continue?"... | |
if(session_attributes.in_continue) { | |
session_attributes.current_step = 0; | |
session_attributes.in_continue = false; | |
new Promise((resolve, reject) => { | |
handlerInput.attributesManager.getPersistentAttributes() | |
.then((attributes) => { | |
attributes.current_step = 0; | |
handlerInput.attributesManager.setPersistentAttributes(attributes); | |
handlerInput.attributesManager.savePersistentAttributes(); | |
resolve(); | |
}) | |
.catch((error) => { | |
reject(error); | |
}) | |
}); | |
setSession(handlerInput, session_attributes); | |
speechText += session_attributes.name ? `Ok, ${session_attributes.name}. ` : ''; | |
speechText += languageStrings['MENU']; | |
return buildResponse(handlerInput, speechText); | |
} else { | |
const current_step_object = languageStrings['PROCEDURES'][session_attributes.procedure]['STEPS'][session_attributes.current_step]; | |
speechText += session_attributes.name ? `Ok, ${session_attributes.name}. ` : ''; | |
speechText += current_step_object['speech']; | |
return buildResponse(handlerInput, speechText); | |
} | |
} | |
}; | |
const GetNextStepHandler = { | |
canHandle : function(handlerInput) { | |
return checkIntentType(handlerInput, 'IntentRequest') | |
&& checkIntentName(handlerInput, 'GetNextStep'); | |
}, | |
handle : function(handlerInput) { | |
let speechText = ''; | |
let session_attributes = getSession(handlerInput); | |
if (session_attributes.current_step < languageStrings['PROCEDURES'][session_attributes.procedure]['STEPS'].length - 1) { | |
// if they replied "yes" to "would you like to continue?"... | |
if(session_attributes.in_continue) { | |
session_attributes.in_continue = false; | |
} else { | |
session_attributes.current_step += 1; | |
new Promise((resolve, reject) => { | |
handlerInput.attributesManager.getPersistentAttributes() | |
.then((attributes) => { | |
attributes.current_step += 1; | |
handlerInput.attributesManager.setPersistentAttributes(attributes); | |
handlerInput.attributesManager.savePersistentAttributes(); | |
resolve(); | |
}) | |
.catch((error) => { | |
reject(error); | |
}) | |
}); | |
} | |
setSession(handlerInput, session_attributes); | |
} | |
const current_step_object = languageStrings['PROCEDURES'][session_attributes.procedure]['STEPS'][session_attributes.current_step]; | |
speechText += session_attributes.name ? `Ok, ${session_attributes.name}. ` : ''; | |
speechText += current_step_object['speech']; | |
return (session_attributes.current_step === languageStrings['PROCEDURES'][session_attributes.procedure]['STEPS'].length - 1) ? | |
buildFinalResponse(handlerInput, speechText) : | |
buildResponse(handlerInput, speechText); | |
} | |
}; | |
const GetPreviousStepHandler = { | |
canHandle : function(handlerInput) { | |
return checkIntentType(handlerInput, 'IntentRequest') | |
&& checkIntentName(handlerInput, 'GetPreviousStep'); | |
}, | |
handle : function(handlerInput) { | |
let speechText = ''; | |
let session_attributes = getSession(handlerInput); | |
if (session_attributes.current_step > 0) { | |
session_attributes.current_step -= 1; | |
setSession(handlerInput, session_attributes); | |
new Promise((resolve, reject) => { | |
handlerInput.attributesManager.getPersistentAttributes() | |
.then((attributes) => { | |
attributes.current_step -= 1; | |
handlerInput.attributesManager.setPersistentAttributes(attributes); | |
handlerInput.attributesManager.savePersistentAttributes(); | |
resolve(); | |
}) | |
.catch((error) => { | |
reject(error); | |
}) | |
}); | |
} | |
const current_step_object = languageStrings['PROCEDURES'][session_attributes.procedure]['STEPS'][session_attributes.current_step]; | |
speechText += session_attributes.name ? `Ok, ${session_attributes.name}. ` : ''; | |
speechText += current_step_object['speech']; | |
return buildResponse(handlerInput, speechText); | |
} | |
}; | |
const SkipStepHandler = { | |
canHandle : function(handlerInput) { | |
return checkIntentType(handlerInput, 'IntentRequest') | |
&& checkIntentName(handlerInput, 'SkipStep'); | |
}, | |
handle : function(handlerInput) { | |
let speechText = ''; | |
let session_attributes = getSession(handlerInput); | |
speechText += session_attributes.name ? `Ok, ${session_attributes.name}. ` : ''; | |
if (session_attributes.current_step < languageStrings['PROCEDURES'][session_attributes.procedure]['STEPS'].length - 1) { | |
const required = languageStrings['PROCEDURES'][session_attributes.procedure]['STEPS'][session_attributes.current_step]['required']; | |
if (!required) { | |
session_attributes.current_step += 1; | |
setSession(handlerInput, session_attributes); | |
new Promise((resolve, reject) => { | |
handlerInput.attributesManager.getPersistentAttributes() | |
.then((attributes) => { | |
attributes.current_step += 1; | |
handlerInput.attributesManager.setPersistentAttributes(attributes); | |
handlerInput.attributesManager.savePersistentAttributes(); | |
resolve(); | |
}) | |
.catch((error) => { | |
reject(error); | |
}) | |
}); | |
} else { | |
speechText += languageStrings['CANTSKIP'] + ' '; | |
speechText += languageStrings['PROCEDURES'][session_attributes.procedure]['STEPS'][session_attributes.current_step]['why_cant_skip'] ? languageStrings['PROCEDURES'][session_attributes.procedure]['STEPS'][session_attributes.current_step]['why_cant_skip'] + ' ' : ''; | |
} | |
} | |
const current_step_object = languageStrings['PROCEDURES'][session_attributes.procedure]['STEPS'][session_attributes.current_step]; | |
speechText += current_step_object['speech']; | |
return (session_attributes.current_step === languageStrings['PROCEDURES'][session_attributes.procedure]['STEPS'].length - 1) ? | |
buildFinalResponse(handlerInput, speechText) : | |
buildResponse(handlerInput, speechText); | |
} | |
}; | |
const RestartHandler = { | |
canHandle : function(handlerInput) { | |
return checkIntentType(handlerInput, 'IntentRequest') | |
&& checkIntentName(handlerInput, 'Restart'); | |
}, | |
handle : function(handlerInput) { | |
let speechText = ''; | |
let session_attributes = getSession(handlerInput); | |
speechText += session_attributes.name ? `Ok, ${session_attributes.name}. ` : ''; | |
speechText += `Let's start over. `; | |
session_attributes.current_step = 0; | |
setSession(handlerInput, session_attributes); | |
new Promise((resolve, reject) => { | |
handlerInput.attributesManager.getPersistentAttributes() | |
.then((attributes) => { | |
attributes.current_step = 0; | |
handlerInput.attributesManager.setPersistentAttributes(attributes); | |
handlerInput.attributesManager.savePersistentAttributes(); | |
resolve(); | |
}) | |
.catch((error) => { | |
reject(error); | |
}) | |
}); | |
const current_step_object = languageStrings['PROCEDURES'][session_attributes.procedure]['STEPS'][session_attributes.current_step]; | |
speechText += current_step_object['speech']; | |
return buildResponse(handlerInput, speechText); | |
} | |
}; | |
const MyNameIntent = { | |
canHandle : function(handlerInput) { | |
return checkIntentType(handlerInput, 'IntentRequest') | |
&& checkIntentName(handlerInput, 'MyName'); | |
}, | |
handle : function(handlerInput) { | |
let session_attributes = getSession(handlerInput); | |
session_attributes.name = handlerInput.requestEnvelope.request && | |
handlerInput.requestEnvelope.request.intent && | |
handlerInput.requestEnvelope.request.intent.slots && | |
handlerInput.requestEnvelope.request.intent.slots.user_name && | |
handlerInput.requestEnvelope.request.intent.slots.user_name.value; | |
setSession(handlerInput, session_attributes); | |
new Promise((resolve, reject) => { | |
handlerInput.attributesManager.getPersistentAttributes() | |
.then((attributes) => { | |
attributes.name = handlerInput.requestEnvelope.request && | |
handlerInput.requestEnvelope.request.intent && | |
handlerInput.requestEnvelope.request.intent.slots && | |
handlerInput.requestEnvelope.request.intent.slots.user_name && | |
handlerInput.requestEnvelope.request.intent.slots.user_name.value; | |
handlerInput.attributesManager.setPersistentAttributes(attributes); | |
handlerInput.attributesManager.savePersistentAttributes(); | |
resolve(); | |
}) | |
.catch((error) => { | |
reject(error); | |
}) | |
}); | |
const speechText = `Thanks, ${session_attributes.name}. I'll call you that from now on.`; | |
return buildResponse(handlerInput, speechText); | |
} | |
}; | |
const DeleteNameIntent = { | |
canHandle : function(handlerInput) { | |
return checkIntentType(handlerInput, 'IntentRequest') | |
&& checkIntentName(handlerInput, 'DeleteName'); | |
}, | |
handle : function(handlerInput) { | |
let session_attributes = getSession(handlerInput); | |
delete session_attributes.name; | |
setSession(handlerInput, session_attributes); | |
new Promise((resolve, reject) => { | |
handlerInput.attributesManager.getPersistentAttributes() | |
.then((attributes) => { | |
delete attributes.name; | |
handlerInput.attributesManager.setPersistentAttributes(attributes); | |
handlerInput.attributesManager.savePersistentAttributes(); | |
resolve(); | |
}) | |
.catch((error) => { | |
reject(error); | |
}) | |
}); | |
const speechText = `I deleted your name, whoever you are.`; | |
return buildResponse(handlerInput, speechText); | |
} | |
}; | |
const HelpIntentHandler = { | |
canHandle : function(handlerInput) { | |
return checkIntentType(handlerInput, 'IntentRequest') | |
&& checkIntentName(handlerInput, 'AMAZON.HelpIntent'); | |
}, | |
handle : function(handlerInput){ | |
const speechText = 'This alexa skill guides you through a procedure.'; | |
return buildResponse(handlerInput, speechText); | |
} | |
}; | |
const SessionEndedRequestHandler = { | |
canHandle : function(handlerInput) { | |
return checkIntentType(handlerInput, 'SessionEndedRequest'); | |
}, | |
handle : function(handlerInput) { | |
// TODO: Save session variables back to database | |
return handlerInput.responseBuilder.getResponse(); | |
} | |
}; | |
const ErrorHandler = { | |
canHandle : function() { | |
return true; | |
}, | |
handle : function(handlerInput, error) { | |
return buildResponse(handlerInput, `Sorry, I can't understand the command. Please say again.`); | |
} | |
}; | |
const ContinueHandler = { | |
canHandle : function(handlerInput) { | |
return checkIntentType(handlerInput, 'IntentRequest') | |
&& checkIntentName(handlerInput, 'Continue'); | |
}, | |
handle : function(handlerInput) { | |
let session_attributes = getSession(handlerInput); | |
session_attributes.in_continue = true; | |
setSession(handlerInput, session_attributes); | |
let speechText = ''; | |
speechText += `Welcome back, ${session_attributes.name ? ', ' + session_attributes.name : ''}. You left last time on step ${session_attributes.current_step} of the ${session_attributes.procedure} procedure. `; | |
speechText += languageStrings['CONTINUE']; | |
return buildResponse(handlerInput, speechText); | |
} | |
}; | |
const LaunchRequestHandler = { | |
canHandle : function(handlerInput) { | |
return checkIntentType(handlerInput, 'LaunchRequest'); | |
}, | |
handle : function(handlerInput) { | |
let session_attributes = getSession(handlerInput); | |
let speechText = ''; | |
speechText += session_attributes.name ? `Welcome back, ${session_attributes.name}. ` : ''; | |
// speechText += session_attributes.procedureCount ? `You've started this procedure ${session_attributes.procedureCount} times. ` : ''; | |
session_attributes.current_step = 0; | |
setSession(handlerInput, session_attributes); | |
const current_step_object = languageStrings['PROCEDURES'][session_attributes.procedure]['STEPS'][session_attributes.current_step]; | |
speechText += current_step_object['speech']; | |
return buildResponse(handlerInput, speechText); | |
} | |
}; | |
const MenuHandler = { | |
canHandle : function(handlerInput) { | |
return checkIntentType(handlerInput, 'IntentRequest') | |
&& checkIntentName(handlerInput, 'Menu'); | |
}, | |
handle : function(handlerInput) { | |
let speechText = ''; | |
let session_attributes = getSession(handlerInput); | |
speechText += session_attributes.name ? `Ok, ${session_attributes.name}. ` : ''; | |
speechText += languageStrings['MENU']; | |
return buildResponse(handlerInput, speechText); | |
} | |
}; | |
const assistSelectHandler = { | |
canHandle : function(handlerInput) { | |
return checkIntentType(handlerInput, 'IntentRequest') | |
&& checkIntentName(handlerInput, 'assistSelect'); | |
}, | |
handle : function(handlerInput) { | |
let session_attributes = getSession(handlerInput); | |
session_attributes.procedure = handlerInput.requestEnvelope.request && | |
handlerInput.requestEnvelope.request.intent && | |
handlerInput.requestEnvelope.request.intent.slots && | |
handlerInput.requestEnvelope.request.intent.slots.Procedure && | |
handlerInput.requestEnvelope.request.intent.slots.Procedure.resolutions && | |
handlerInput.requestEnvelope.request.intent.slots.Procedure.resolutions.resolutionsPerAuthority && | |
handlerInput.requestEnvelope.request.intent.slots.Procedure.resolutions.resolutionsPerAuthority[0] && | |
handlerInput.requestEnvelope.request.intent.slots.Procedure.resolutions.resolutionsPerAuthority[0].values && | |
handlerInput.requestEnvelope.request.intent.slots.Procedure.resolutions.resolutionsPerAuthority[0].values[0] && | |
handlerInput.requestEnvelope.request.intent.slots.Procedure.resolutions.resolutionsPerAuthority[0].values[0].value && | |
handlerInput.requestEnvelope.request.intent.slots.Procedure.resolutions.resolutionsPerAuthority[0].values[0].value.id; | |
session_attributes.current_step = 0; | |
setSession(handlerInput, session_attributes); | |
new Promise((resolve, reject) => { | |
handlerInput.attributesManager.getPersistentAttributes() | |
.then((attributes) => { | |
attributes.procedure = session_attributes.procedure; | |
attributes.current_step = session_attributes.current_step = 0; | |
handlerInput.attributesManager.setPersistentAttributes(attributes); | |
handlerInput.attributesManager.savePersistentAttributes(); | |
resolve(); | |
}) | |
.catch((error) => { | |
reject(error); | |
}) | |
}); | |
const current_step_object = languageStrings['PROCEDURES'][session_attributes.procedure]['STEPS'][session_attributes.current_step]; | |
const speechText = current_step_object['speech']; | |
return buildResponse(handlerInput, speechText); | |
} | |
}; | |
/** | |
* If this is the first start of the skill, grab the user's data from Dynamo and | |
* set the session attributes to the persistent data. | |
*/ | |
const GetUserDataInterceptor = { | |
process(handlerInput) { | |
// Launch Request | |
if (checkIntentType(handlerInput, 'LaunchRequest')) { | |
return new Promise((resolve, reject) => { | |
handlerInput.attributesManager.getPersistentAttributes() | |
.then((attributes) => { | |
// increment procedure count | |
attributes.procedureCount = attributes.procedureCount ? attributes.procedureCount + 1 : 1; | |
// set current step | |
attributes.current_step = attributes.current_step || 0; | |
if (attributes.procedure && attributes.current_step >= languageStrings['PROCEDURES'][attributes.procedure]['STEPS'].length - 1) { | |
attributes.current_step = 0; | |
} | |
// save to db | |
handlerInput.attributesManager.setPersistentAttributes(attributes); | |
handlerInput.attributesManager.savePersistentAttributes(); | |
//if left off somewhere between first and last intent, route to continue handler | |
if (attributes.procedure && attributes.current_step && attributes.current_step > 0 && (attributes.current_step < languageStrings['PROCEDURES'][attributes.procedure]['STEPS'].length - 1)) { | |
handlerInput.requestEnvelope.request.type = 'IntentRequest'; | |
handlerInput.requestEnvelope.request.intent = { name : 'Continue' }; | |
} else { | |
handlerInput.requestEnvelope.request.type = 'IntentRequest'; | |
handlerInput.requestEnvelope.request.intent = { name : 'Menu' }; | |
} | |
// set persistent variables to session variables | |
setSession(handlerInput, attributes); | |
resolve(); | |
}) | |
.catch((error) => { | |
reject(error); | |
}) | |
}); | |
} | |
} | |
}; | |
module.exports = { | |
requestHandlers: [ | |
CancelAndStopIntentHandler, | |
HelpIntentHandler, | |
MenuHandler, | |
assistSelectHandler, | |
LaunchRequestHandler, | |
MyNameIntent, | |
DeleteNameIntent, | |
GetNextStepHandler, | |
GetPreviousStepHandler, | |
GetCurrentStepHandler, | |
SkipStepHandler, | |
RestartHandler, | |
ContinueHandler, | |
SessionEndedRequestHandler, | |
], | |
requestInterceptors: [ | |
GetUserDataInterceptor | |
], | |
errorHandlers: [ | |
ErrorHandler | |
] | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* eslint-disable func-names */ | |
/* eslint-disable no-//console */ | |
'use strict'; | |
const Alexa = require('ask-sdk'); | |
const handlers = require('./src/handlers'); | |
exports.handler = Alexa.SkillBuilders | |
.standard() | |
.withSkillId('myskillid12345') | |
.addRequestInterceptors(...handlers.requestInterceptors) | |
.addRequestHandlers(...handlers.requestHandlers) | |
.addErrorHandlers(...handlers.errorHandlers) | |
.withTableName('my-table-name') | |
.withAutoCreateTable(true) | |
.withDynamoDbClient() | |
.lambda(); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Create SSML for a countdown from a specified number down to 1 at the specified interval | |
* | |
* @param {string} numSeconds number to start the countdown from | |
* @param {number} breakTime wait time between counts (default is 0.85s) | |
* @return {string} SSML for the full countdown | |
*/ | |
function countDown(numSeconds, breakTime) { | |
return Array.apply(null, {length: numSeconds}) | |
.map((n, i) => {return `<say-as interpret-as="cardinal">${numSeconds-i}</say-as>` }) | |
.join(`<break time="${breakTime ? breakTime : 0.3}s" />`) + `<break time="${breakTime ? breakTime : 0.5}s" />`; | |
} | |
/** | |
* Create SSML for a break of a specified time period in seconds | |
* @param {number} numSeconds number of seconds for alexa to break | |
* @return {string} SSML for a break of a specified number of seconds | |
*/ | |
function breakFor(numSeconds) { | |
return `<break time="${numSeconds}s" />`; | |
} | |
/** | |
* Create SSML for a break appropriate between paragraphs | |
* @return {string} SSML for a break between paragraphs | |
*/ | |
function endParagraph() { | |
return breakFor(1.25); | |
} | |
/** | |
* Get session variables | |
* @param {Object} handlerInput request object | |
* @return {Object} attributes from the request object | |
*/ | |
function getSession(handlerInput) { | |
return handlerInput.attributesManager.getSessionAttributes(); | |
} | |
/** | |
* Set session variables | |
* @param {Object} handlerInput request object | |
* @param {Object} attributes attributes to set in request object | |
*/ | |
function setSession(handlerInput, attributes) { | |
handlerInput.attributesManager.setSessionAttributes(attributes); | |
} | |
/** | |
* Build the request object to send to alexa | |
* | |
* @param {Object} handlerInput the request object goign to the Alexa service | |
* @param {string} speakText string for alexa to speak to user | |
* @param {string} repromptText reprompt string for alexa to speak to user | |
* @return {Object} response object sent to Alexa to act out | |
*/ | |
function buildResponse(handlerInput, speakText, repromptText) { | |
return handlerInput.responseBuilder | |
.speak(speakText) | |
.reprompt(repromptText || speakText) | |
.getResponse(); | |
} | |
/** | |
* Build the request object to send to alexa and close the session | |
* | |
* @param {Object} handlerInput the request object goign to the Alexa service | |
* @param {string} speakText string for alexa to speak to user | |
* @return {Object} response object sent to Alexa to act out | |
*/ | |
function buildFinalResponse(handlerInput, speakText) { | |
return handlerInput.responseBuilder | |
.speak(speakText) | |
.withShouldEndSession(true) | |
.getResponse(); | |
} | |
/** | |
* Compare intent type to an input string | |
* @param {Object} handlerInput the request object goign to the Alexa service | |
* @param {string} intentType type of intent to check | |
* @return {boolean} whether or not intent is of the same intent type | |
*/ | |
function checkIntentType(handlerInput, intentType) { | |
return handlerInput.requestEnvelope.request.type === intentType; | |
} | |
/** | |
* Compare intent name to an input string | |
* @param {Object} handlerInput the request object goign to the Alexa service | |
* @param {string} intentType name of intent to check | |
* @return {boolean} whether or not intent is of the same intent name | |
*/ | |
function checkIntentName(handlerInput, intentName) { | |
return handlerInput.requestEnvelope.request.intent.name === intentName; | |
} | |
module.exports = { | |
countDown: countDown, | |
breakFor: breakFor, | |
endParagraph: endParagraph, | |
getSession: getSession, | |
setSession: setSession, | |
buildResponse: buildResponse, | |
buildFinalResponse: buildFinalResponse, | |
checkIntentType: checkIntentType, | |
checkIntentName: checkIntentName, | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment