Skip to content

Instantly share code, notes, and snippets.

@DanLaufer
Last active October 19, 2018 18:48
Show Gist options
  • Save DanLaufer/75e780b2fabb747bbc1a4fb01a588e06 to your computer and use it in GitHub Desktop.
Save DanLaufer/75e780b2fabb747bbc1a4fb01a588e06 to your computer and use it in GitHub Desktop.
Alexa Skill - Example including relevant JS files
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
]
};
/* 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();
/**
* 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