Last active
March 28, 2019 01:07
-
-
Save softwarechido/c66d267377e42ed62b07350fa3bc4024 to your computer and use it in GitHub Desktop.
This file contains 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
// 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(); |
This file contains 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
{ | |
"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?" | |
} | |
] | |
} | |
] | |
} | |
} |
This file contains 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
{ | |
"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