Skip to content

Instantly share code, notes, and snippets.

@softwarechido
Last active March 28, 2019 01:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save softwarechido/c66d267377e42ed62b07350fa3bc4024 to your computer and use it in GitHub Desktop.
Save softwarechido/c66d267377e42ed62b07350fa3bc4024 to your computer and use it in GitHub Desktop.
// This sample demonstrates handling intents from an Alexa skill using the Alexa Skills Kit SDK (v2).
// Please visit https://alexa.design/cookbook for additional examples on implementing slots, dialog management,
// session persistence, api calls, and more.
const Alexa = require('ask-sdk-core');
const persistenceAdapter = require('ask-sdk-s3-persistence-adapter');
// i18n dependencies. i18n is the main module, sprintf allows us to include variables with '%s'.
const i18n = require('i18next');
const sprintf = require('i18next-sprintf-postprocessor');
// We create a language strings object containing all of our strings.
// The keys for each string will then be referenced in our code
// e.g. requestAttributes.t('WELCOME')
const languageStrings = {
'en' : {
'translation' : {
'WELCOME': 'Hello! Welcome to Cakewalk. What is your birthday?',
'WELCOME_REPROMPT': 'I was born Nov. 6th, 2015. When were you born?',
'HELP': 'You can say hello to me! How can I help?',
'HAPPY_BDAY': 'Happy %sth birthday',
'NOT_BDAY': 'Welcome back. It looks like there are %s days until your %sth birthday.',
'SAVE_CONFIRMATION': 'Thanks, I\'ll remember that you were born %s %s, %s.',
'GOODBYE': 'Goodbye!',
'INTENT_TRIGGER': 'You just triggered %s',
'SERVICE_ERROR': 'There was a problem connecting to the service.',
'GENERIC_ERROR': 'Sorry, I couldn\'t understand what you said.Please try again.'
}
},
'es-MX' : {
'translation' : {
'WELCOME': 'Hola! Bienvenido a paseo pastelero. ¿Cuándo es tu cumpleaños?',
'WELCOME_REPROMPT': 'Yo nací el 6 de noviembre del 2016, ¿tú cuándo naciste?',
'HELP': 'Me puedes decir Hola! ¿Cómo te puedo ayudar?',
'HAPPY_BDAY': 'Feliz cumpleaños número %s',
'NOT_BDAY': 'Holi! faltan %s días para tu cumpleaños número %s',
'SAVE_CONFIRMATION': 'Gracias, recordaré que naciste el %s %s, %s.',
'GOODBYE': 'Adios!',
'INTENT_TRIGGER': 'Lanzaste el intent que se llama %s',
'SERVICE_ERROR': 'Hubo un problema de conexión con el servicio',
'GENERIC_ERROR': 'Lo siento, No puedo entender lo que has dicho. Por favor inténtalo de nuevo'
}
}
}
// Slot Adapters
function monthNameToNum(monthname) {
var month = months.indexOf(monthname);
return month ? month + 1 : 0;
}
const months = ['enero', 'febrero', 'marzo', 'abril', 'mayo',
'junio', 'julio', 'agosto', 'septiembre',
'octubre', 'noviembre', 'diciembre'
];
const LaunchRequestHandler = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === 'LaunchRequest';
},
handle(handlerInput) {
// we get the requestAttributes that our localization function is tied to.
const requestAttributes = handlerInput.attributesManager.getRequestAttributes();
// we reference the keys we defined above in the languageStrings object.
const speechText = requestAttributes.t('WELCOME');
const repromptText = requestAttributes.t('WELCOME_REPROMPT');
return handlerInput.responseBuilder
.speak(speechText)
.reprompt(repromptText)
.getResponse();
}
};
const HasBirthdayLaunchRequestHandler = {
canHandle(handlerInput) {
const attributesManager = handlerInput.attributesManager;
const sessionAttributes = attributesManager.getSessionAttributes() || {};
const year = sessionAttributes.hasOwnProperty('year') ? sessionAttributes.year : 0;
const month = sessionAttributes.hasOwnProperty('month') ? sessionAttributes.month : 0;
const day = sessionAttributes.hasOwnProperty('day') ? sessionAttributes.day : 0;
return handlerInput.requestEnvelope.request.type === 'LaunchRequest' &&
year &&
month &&
day;
},
async handle(handlerInput) {
const serviceClientFactory = handlerInput.serviceClientFactory;
const deviceId = handlerInput.requestEnvelope.context.System.device.deviceId;
const sessionAttributes = handlerInput.attributesManager.getSessionAttributes() || {};
const requestAttributes = handlerInput.attributesManager.getRequestAttributes();
const year = sessionAttributes.hasOwnProperty('year') ? sessionAttributes.year : 0;
const month = sessionAttributes.hasOwnProperty('month') ? sessionAttributes.month : 0;
const day = sessionAttributes.hasOwnProperty('day') ? sessionAttributes.day : 0;
let userTimeZone;
try {
const upsServiceClient = serviceClientFactory.getUpsServiceClient();
userTimeZone = await upsServiceClient.getSystemTimeZone(deviceId);
} catch (error) {
if (error.name !== 'ServiceError') {
return handlerInput.responseBuilder.speak(requestAttributes.t('SERVICE_ERROR')).getResponse();
}
console.log('error', error.message);
}
console.log('userTimeZone', userTimeZone);
const oneDay = 24*60*60*1000;
// getting the current date with the time
const currentDateTime = new Date(new Date().toLocaleString("en-US", {timeZone: userTimeZone}));
// removing the time from the date because it affects our difference calculation
const currentDate = new Date(currentDateTime.getFullYear(), currentDateTime.getMonth(), currentDateTime.getDate());
const currentYear = currentDate.getFullYear();
console.log('currentDateTime:', currentDateTime);
console.log('currentDate:', currentDate);
// getting getting the next birthday
const monthAsNum = monthNameToNum(month);
let nextBirthday = Date.parse(`${monthAsNum} ${day}, ${currentYear}`);
console.log('nextBirthday:', nextBirthday);
// adjust the nextBirthday by one year if the current date is after their birthday
if (currentDate.getTime() > nextBirthday) {
nextBirthday = Date.parse(`${month} ${day}, ${currentYear + 1}`);
}
// setting the default speechText to Happy xth Birthday!!
// Alexa will automatically correct the ordinal for you.
// no need to worry about when to use st, th, rd
// i18n: we can pass in variables as arguments of the 't' function
// and they will replace (in order) any %s in the string.
let speechText = requestAttributes.t('HAPPY_BDAY', currentYear - year);
if (currentDate.getTime() !== nextBirthday) {
const diffDays = Math.round(Math.abs((currentDate.getTime() - nextBirthday)/oneDay));
speechText = requestAttributes.t('NOT_BDAY', diffDays, currentYear - year);
}
return handlerInput.responseBuilder
.speak(speechText)
.getResponse();
}
};
const BirthdayIntentHandler = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === 'IntentRequest'
&& handlerInput.requestEnvelope.request.intent.name === 'BirthdayIntent';
},
async handle(handlerInput) {
const year = handlerInput.requestEnvelope.request.intent.slots.year.value;
const month = handlerInput.requestEnvelope.request.intent.slots.month.value;
const day = handlerInput.requestEnvelope.request.intent.slots.day.value;
const attributesManager = handlerInput.attributesManager;
const requestAttributes = attributesManager.getRequestAttributes();
const birthdayAttributes = {
"year": year,
"month": month,
"day": day
};
attributesManager.setPersistentAttributes(birthdayAttributes);
await attributesManager.savePersistentAttributes();
const speechText = requestAttributes.t('SAVE_CONFIRMATION', month, day, year);
return handlerInput.responseBuilder
.speak(speechText)
//.reprompt('add a reprompt if you want to keep the session open for the user to respond')
.getResponse();
}
};
const HelpIntentHandler = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === 'IntentRequest'
&& handlerInput.requestEnvelope.request.intent.name === 'AMAZON.HelpIntent';
},
handle(handlerInput) {
const requestAttributes = handlerInput.attributesManager.getRequestAttributes();
const speechText = requestAttributes.t('HELP');
return handlerInput.responseBuilder
.speak(speechText)
.reprompt(speechText)
.getResponse();
}
};
const CancelAndStopIntentHandler = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === 'IntentRequest'
&& (handlerInput.requestEnvelope.request.intent.name === 'AMAZON.CancelIntent'
|| handlerInput.requestEnvelope.request.intent.name === 'AMAZON.StopIntent');
},
handle(handlerInput) {
const requestAttributes = handlerInput.attributesManager.getRequestAttributes();
const speechText = requestAttributes.t('GOODBYE');
return handlerInput.responseBuilder
.speak(speechText)
.getResponse();
}
};
const SessionEndedRequestHandler = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === 'SessionEndedRequest';
},
handle(handlerInput) {
// Any cleanup logic goes here.
return handlerInput.responseBuilder.getResponse();
}
};
// The intent reflector is used for interaction model testing and debugging.
// It will simply repeat the intent the user said. You can create custom handlers
// for your intents by defining them above, then also adding them to the request
// handler chain below.
const IntentReflectorHandler = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === 'IntentRequest';
},
handle(handlerInput) {
const intentName = handlerInput.requestEnvelope.request.intent.name;
const requestAttributes = handlerInput.attributesManager.getRequestAttributes();
const speechText = requestAttributes.t('INTENT_TRIGGER', intentName);
return handlerInput.responseBuilder
.speak(speechText)
//.reprompt('add a reprompt if you want to keep the session open for the user to respond')
.getResponse();
}
};
// Generic error handling to capture any syntax or routing errors. If you receive an error
// stating the request handler chain is not found, you have not implemented a handler for
// the intent being invoked or included it in the skill builder below.
const ErrorHandler = {
canHandle() {
return true;
},
handle(handlerInput, error) {
console.log(`~~~~ Error handled: ${error.message}`);
const requestAttributes = handlerInput.attributesManager.getRequestAttributes();
const speechText = requestAttributes.t('GENERIC_ERROR');
return handlerInput.responseBuilder
.speak(speechText)
.reprompt(speechText)
.getResponse();
}
};
const LoadBirthdayInterceptor = {
async process(handlerInput) {
const attributesManager = handlerInput.attributesManager;
const sessionAttributes = await attributesManager.getPersistentAttributes() || {};
const year = sessionAttributes.hasOwnProperty('year') ? sessionAttributes.year : 0;
const month = sessionAttributes.hasOwnProperty('month') ? sessionAttributes.month : 0;
const day = sessionAttributes.hasOwnProperty('day') ? sessionAttributes.day : 0;
if (year && month && day) {
attributesManager.setSessionAttributes(sessionAttributes);
}
}
}
// This interceptor will bind a translation function 't' to the requestAttributes.
const LocalizationInterceptor = {
process(handlerInput) {
const localizationClient = i18n.use(sprintf).init({
lng: handlerInput.requestEnvelope.request.locale,
resources: languageStrings,
});
localizationClient.localize = function localize() {
const args = arguments;
const values = [];
for (let i = 1; i < args.length; i += 1) {
values.push(args[i]);
}
const value = i18n.t(args[0], {
returnObjects: true,
postProcess: 'sprintf',
sprintf: values,
});
if (Array.isArray(value)) {
return value[Math.floor(Math.random() * value.length)];
}
return value;
};
const attributes = handlerInput.attributesManager.getRequestAttributes();
attributes.t = function translate(...args) {
return localizationClient.localize(...args);
};
},
};
// This handler acts as the entry point for your skill, routing all request and response
// payloads to the handlers above. Make sure any new handlers or interceptors you've
// defined are included below. The order matters - they're processed top to bottom.
exports.handler = Alexa.SkillBuilders.custom()
.withPersistenceAdapter(
new persistenceAdapter.S3PersistenceAdapter({bucketName:process.env.S3_PERSISTENCE_BUCKET})
)
.addRequestHandlers(
HasBirthdayLaunchRequestHandler,
LaunchRequestHandler,
BirthdayIntentHandler,
HelpIntentHandler,
CancelAndStopIntentHandler,
SessionEndedRequestHandler,
IntentReflectorHandler) // make sure IntentReflectorHandler is last so it doesn't override your custom intent handlers
.addErrorHandlers(
ErrorHandler)
.addRequestInterceptors(
LoadBirthdayInterceptor,
LocalizationInterceptor
)
.withApiClient(new Alexa.DefaultApiClient())
.withCustomUserAgent('cookbook/device-settings/v1')
.lambda();
{
"interactionModel": {
"languageModel": {
"invocationName": "paseo pastelero",
"intents": [
{
"name": "AMAZON.CancelIntent",
"samples": []
},
{
"name": "AMAZON.HelpIntent",
"samples": []
},
{
"name": "AMAZON.StopIntent",
"samples": []
},
{
"name": "AMAZON.NavigateHomeIntent",
"samples": []
},
{
"name": "BirthdayIntent",
"slots": [
{
"name": "month",
"type": "AMAZON.Month"
},
{
"name": "day",
"type": "OrdinalSimulation"
},
{
"name": "year",
"type": "AMAZON.FOUR_DIGIT_NUMBER"
}
],
"samples": [
"Nací un {day} de {month} del {year}",
"Nací un {day} de {month} del año {year}",
"Nací el {day} de {month} del año {year}",
"Nací en {month} {day} {year}",
"Nací el {day} de {month} de {year}",
"Nací el {day} de {month} del {year}",
"Nací en {month} {day} ",
"{day} {month} {year}",
"{month} {day}",
"{day} de {month}",
"{month} {day} {year}",
"{month} {year}"
]
}
],
"types": [
{
"name": "OrdinalSimulation",
"values": [
{
"name": {
"value": "31",
"synonyms": [
"trigésimo primero"
]
}
},
{
"name": {
"value": "30",
"synonyms": [
"trigésimo"
]
}
},
{
"name": {
"value": "29",
"synonyms": [
"vigésimo noveno"
]
}
},
{
"name": {
"value": "28",
"synonyms": [
"vigésimo octavo"
]
}
},
{
"name": {
"value": "27",
"synonyms": [
"vigésimo séptimo"
]
}
},
{
"name": {
"value": "26",
"synonyms": [
"vigésimo sexto"
]
}
},
{
"name": {
"value": "25",
"synonyms": [
"vigésimo quinto"
]
}
},
{
"name": {
"value": "24",
"synonyms": [
"vigésimo cuarto"
]
}
},
{
"name": {
"value": "23",
"synonyms": [
"vigésimo tercero"
]
}
},
{
"name": {
"value": "22",
"synonyms": [
"vigésimo segundo"
]
}
},
{
"name": {
"value": "21",
"synonyms": [
"vigésimo primero"
]
}
},
{
"name": {
"value": "20",
"synonyms": [
"vigésimo"
]
}
},
{
"name": {
"value": "19",
"synonyms": [
"décimo noveno"
]
}
},
{
"name": {
"value": "18",
"synonyms": [
"décimo octavo"
]
}
},
{
"name": {
"value": "17",
"synonyms": [
"décimo séptimo"
]
}
},
{
"name": {
"value": "16",
"synonyms": [
"décimo sexto"
]
}
},
{
"name": {
"value": "15",
"synonyms": [
"décimo quinto"
]
}
},
{
"name": {
"value": "14",
"synonyms": [
"décimo cuarto"
]
}
},
{
"name": {
"value": "13",
"synonyms": [
"décimo tercero"
]
}
},
{
"name": {
"value": "12",
"synonyms": [
"décimo segundo"
]
}
},
{
"name": {
"value": "11",
"synonyms": [
"décimo primero"
]
}
},
{
"name": {
"value": "10",
"synonyms": [
"décimo"
]
}
},
{
"name": {
"value": "9",
"synonyms": [
"noveno"
]
}
},
{
"name": {
"value": "8",
"synonyms": [
"octavo"
]
}
},
{
"name": {
"value": "7",
"synonyms": [
"séptimo"
]
}
},
{
"name": {
"value": "6",
"synonyms": [
"sexto"
]
}
},
{
"name": {
"value": "5",
"synonyms": [
"quinto"
]
}
},
{
"name": {
"value": "4",
"synonyms": [
"cuarto"
]
}
},
{
"name": {
"value": "3",
"synonyms": [
"tercero"
]
}
},
{
"name": {
"value": "2",
"synonyms": [
"segundo"
]
}
},
{
"name": {
"value": "1",
"synonyms": [
"primero"
]
}
}
]
}
]
},
"dialog": {
"intents": [
{
"name": "BirthdayIntent",
"confirmationRequired": false,
"prompts": {},
"slots": [
{
"name": "month",
"type": "AMAZON.Month",
"confirmationRequired": false,
"elicitationRequired": true,
"prompts": {
"elicitation": "Elicit.Slot.303899476312.795077103633"
}
},
{
"name": "day",
"type": "OrdinalSimulation",
"confirmationRequired": false,
"elicitationRequired": true,
"prompts": {
"elicitation": "Elicit.Slot.303899476312.985837334781"
}
},
{
"name": "year",
"type": "AMAZON.FOUR_DIGIT_NUMBER",
"confirmationRequired": false,
"elicitationRequired": true,
"prompts": {
"elicitation": "Elicit.Slot.303899476312.27341833344"
}
}
]
}
],
"delegationStrategy": "ALWAYS"
},
"prompts": [
{
"id": "Elicit.Slot.303899476312.795077103633",
"variations": [
{
"type": "PlainText",
"value": "Yo nací en Noviembre. ¿tú cuándo naciste?"
},
{
"type": "PlainText",
"value": "¿En qué mes naciste?"
}
]
},
{
"id": "Elicit.Slot.303899476312.985837334781",
"variations": [
{
"type": "PlainText",
"value": "Yo nací un día seís. ¿tú que día de {month} naciste?"
}
]
},
{
"id": "Elicit.Slot.303899476312.27341833344",
"variations": [
{
"type": "PlainText",
"value": "Yo nací en el año dos mil quince, ¿tu de qué año eres?"
}
]
}
]
}
}
{
"name": "hello-world",
"version": "0.9.0",
"description": "alexa utility for quickly building skills",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Amazon Alexa",
"license": "ISC",
"dependencies": {
"ask-sdk-core": "^2.0.7",
"ask-sdk-model": "^1.4.1",
"aws-sdk": "^2.326.0",
"ask-sdk-s3-persistence-adapter": "^2.5.1",
"i18next": "^13.1.4",
"i18next-sprintf-postprocessor": "^0.2.2"
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment