Skip to content

Instantly share code, notes, and snippets.

@jkelvie
Created December 6, 2019 18:09
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 jkelvie/7139dfe9590b81212658fc9be3d1120b to your computer and use it in GitHub Desktop.
Save jkelvie/7139dfe9590b81212658fc9be3d1120b 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');
const launchDocument = require('./documents/launchDocument.json');
const birthdayDocument = require("./documents/birthdayDocument.json");
const util = require('./util');
const HasBirthdayLaunchRequestHandler = {
canHandle(handlerInput) {
console.log(JSON.stringify(handlerInput.requestEnvelope));
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 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;
let userTimeZone;
try {
const upsServiceClient = serviceClientFactory.getUpsServiceClient();
userTimeZone = await upsServiceClient.getSystemTimeZone(deviceId);
} catch (error) {
if (error.name !== 'ServiceError') {
return handlerInput.responseBuilder.speak("There was a problem connecting to the service.").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());
let currentYear = currentDate.getFullYear();
console.log('currentDateTime:', currentDateTime);
console.log('currentDate:', currentDate);
// getting the next birthday
let nextBirthday = Date.parse(`${month} ${day}, ${currentYear}`);
// 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}`);
currentYear++;
}
// setting the default speakOutput to Happy xth Birthday!!
// Alexa will automatically correct the ordinal for you.
// no need to worry about when to use st, th, rd
const yearsOld = currentYear - year;
let speakOutput = `Happy ${yearsOld}th birthday!`;
let isBirthday = true;
const diffDays = Math.round(Math.abs((currentDate.getTime() - nextBirthday)/oneDay));
if (currentDate.getTime() !== nextBirthday) {
isBirthday = false;
speakOutput = `Welcome back. It looks like there are ${diffDays} days until your ${currentYear - year}th birthday.`
}
const numberDaysString = diffDays === 1 ? "1 day": diffDays + " days";
// Add APL directive to response
// if (Alexa.getSupportedInterfaces(handlerInput.requestEnvelope)['Alexa.Presentation.APL']) {
// if (currentDate.getTime() !== nextBirthday) {
// // Create Render Directive
// handlerInput.responseBuilder.addDirective({
// type: 'Alexa.Presentation.APL.RenderDocument',
// document: launchDocument,
// datasources: {
// text: {
// type: 'object',
// start: "Your Birthday",
// middle: "is in",
// end: numberDaysString
// },
// assets: {
// cake: util.getS3PreSignedUrl('Media/alexaCake_960x960.png'),
// backgroundURL: getBackgroundURL(handlerInput, "lights")
// }
// }
// });
// } else {
// // Create Render Directive
// handlerInput.responseBuilder.addDirective({
// type: 'Alexa.Presentation.APL.RenderDocument',
// document: birthdayDocument,
// datasources: {
// text: {
// type: 'object',
// start: "Happy Birthday!",
// middle: "From,",
// end: "Alexa <3"
// },
// assets: {
// video: "https://public-pics-muoio.s3.amazonaws.com/video/Amazon_Cake.mp4",
// backgroundURL: getBackgroundURL(handlerInput, "confetti")
// }
// }
// });
// }
// }
handlerInput.responseBuilder.addDirective({
type: 'Alexa.Presentation.APL.RenderDocument',
document: birthdayDocument,
datasources: {
text: {
type: 'object',
start: "Happy Birthday!",
middle: "From,",
end: "Alexa <3"
},
assets: {
video: "https://public-pics-muoio.s3.amazonaws.com/video/Amazon_Cake.mp4",
backgroundURL: getBackgroundURL(handlerInput, "confetti")
}
}
});
const response = handlerInput.responseBuilder
.speak(speakOutput)
.getResponse();
console.log("Response: " + JSON.stringify(response, null, 2));
return response;
}
};
const LaunchRequestHandler = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === 'LaunchRequest';
},
handle(handlerInput) {
const speakOutput = 'Hello! Welcome to Cake walk. What is your birthday?';
const repromptOutput = 'I was born Nov. 6th, 2015. When were you born?';
console.log("log from launch request");
// Add APL directive to response
if (Alexa.getSupportedInterfaces(handlerInput.requestEnvelope)['Alexa.Presentation.APL']) {
// Create Render Directive
handlerInput.responseBuilder.addDirective({
type: 'Alexa.Presentation.APL.RenderDocument',
document: launchDocument,
datasources: {
text: {
type: 'object',
start: "Welcome",
middle: "to",
end: "Cake Walk!"
},
assets: {
cake: util.getS3PreSignedUrl('Media/alexaCake_960x960.png'),
backgroundURL: getBackgroundURL(handlerInput, "lights")
}
}
});
}
return handlerInput.responseBuilder
.speak(speakOutput)
.reprompt(repromptOutput)
.getResponse();
}
};
const BirthdayIntentHandler = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === 'IntentRequest'
&& handlerInput.requestEnvelope.request.intent.name === 'CaptureBirthdayIntent';
},
async handle(handlerInput) {
console.log("log from BirthdayIntentHandler");
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 birthdayAttributes = {
"year": year,
"month": month,
"day": day
};
attributesManager.setPersistentAttributes(birthdayAttributes);
await attributesManager.savePersistentAttributes();
const headerMessage = "CaptureBirthdayIntent";
const hintString = "This is my hint";
const speakOutput = `Thanks, I'll remember that you were born ${month} ${day} ${year}.`;
return handlerInput.responseBuilder
.speak(speakOutput)
.withShouldEndSession(true)
.getResponse();
}
};
const HelpIntentHandler = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === 'IntentRequest'
&& handlerInput.requestEnvelope.request.intent.name === 'AMAZON.HelpIntent';
},
handle(handlerInput) {
const speakOutput = 'You can say hello to me! How can I help?';
return handlerInput.responseBuilder
.speak(speakOutput)
.reprompt(speakOutput)
.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 speakOutput = 'Goodbye!';
return handlerInput.responseBuilder
.speak(speakOutput)
.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 speakOutput = `You just triggered ${intentName}`;
return handlerInput.responseBuilder
.speak(speakOutput)
//.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 speakOutput = `Sorry, I couldn't understand what you said. Please try again.`;
return handlerInput.responseBuilder
.speak(speakOutput)
.reprompt(speakOutput)
.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);
}
}
}
function getBackgroundURL(handlerInput, fileNamePrefix) {
const viewportProfile = Alexa.getViewportProfile(handlerInput.requestEnvelope);
const backgroundKey = viewportProfile === 'TV-LANDSCAPE-XLARGE' ? "Media/"+fileNamePrefix+"_1920x1080.png" : "Media/"+fileNamePrefix+"_1280x800.png";
return util.getS3PreSignedUrl(backgroundKey);
}
// 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
)
.withApiClient(new Alexa.DefaultApiClient())
.lambda();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment