├── Chapter02 ├── lambda1 │ └── index.js ├── lambda2 │ └── index.js └── lambda3 │ ├── hr.js │ └── index.js ├── Chapter03 ├── basicSkill │ ├── archive.zip │ ├── index.js │ ├── package-lock.json │ └── package.json ├── car-data │ ├── fiat500.json │ ├── fordCmax.json │ ├── fordFiesta.json │ ├── hyundaiI10.json │ ├── mercedesEClass.json │ ├── peugeot208.json │ ├── scodaOctaviaAuto.json │ ├── vauxhallAstra.json │ ├── vauxhallInsignia.json │ └── vwGolf.json └── suggestCar │ ├── archive.zip │ ├── index.js │ ├── package-lock.json │ └── package.json ├── Chapter04 ├── boilerplate Lambda │ ├── index.js │ ├── package-lock.json │ └── package.json ├── weatherGods-2 │ ├── archive.zip │ ├── index.js │ ├── package-lock.json │ └── package.json └── weatherGods │ ├── archive.zip │ ├── index.js │ ├── package-lock.json │ └── package.json ├── Chapter05 ├── All-Lex-Responses.js ├── CL-other │ ├── archive.zip │ ├── index.js │ ├── package-lock.json │ └── package.json ├── CL-setup │ ├── archive.zip │ ├── index.js │ ├── package-lock.json │ └── package.json ├── CL-users │ ├── archive.zip │ ├── index.js │ ├── package-lock.json │ └── package.json └── data │ ├── faq-other.json │ ├── faq-setup.json │ └── faq-users.json ├── Chapter06 ├── addToCart │ ├── DB.js │ ├── LexResponses.js │ ├── archive.zip │ ├── index.js │ ├── package-lock.json │ └── tests ├── checkout │ ├── DB.js │ ├── LexResponses.js │ ├── archive.zip │ ├── index.js │ ├── package-lock.json │ └── tests ├── data │ └── stock.json ├── getSavedCart │ ├── DB.js │ ├── LexResponses.js │ ├── archive.zip │ ├── index.js │ ├── package-lock.json │ └── tests ├── productFind │ ├── LexResponses.js │ ├── archive.zip │ ├── index.js │ ├── package-lock.json │ └── tests ├── saveCart │ ├── DB.js │ ├── LexResponses.js │ ├── index.js │ └── tests └── whatsInMyCart │ ├── DB.js │ ├── LexResponses.js │ ├── archive.zip │ ├── index.js │ ├── package-lock.json │ └── tests ├── Chapter07 ├── chatbotUI │ ├── index.html │ ├── script.js │ └── style.css └── lex-shopping-api │ ├── archive.zip │ ├── index.js │ └── package-lock.json ├── Chapter08 ├── data │ └── stock.json ├── productFind │ ├── LexResponses.js │ ├── archive.zip │ ├── index.js │ ├── package-lock.json │ └── tests └── weatherGods │ ├── archive.zip │ ├── index.js │ ├── package-lock.json │ └── package.json ├── LICENSE ├── README.md └── build.sh /Chapter02/lambda1/index.js: -------------------------------------------------------------------------------- 1 | exports.handler = async (event) => { 2 | // TODO implement 3 | console.log(event); 4 | let { 5 | name, 6 | age 7 | } = event; 8 | // same as => let name = event.name; let age = event.age 9 | 10 | return 'Hello from Lambda!' 11 | }; -------------------------------------------------------------------------------- /Chapter02/lambda2/index.js: -------------------------------------------------------------------------------- 1 | exports.handler = async (event) => { 2 | // TODO implement 3 | console.log(event); 4 | let { 5 | name, 6 | age 7 | } = event; 8 | // same as => let name = event.name; let age = event.age 9 | 10 | return createString(name, age); 11 | }; 12 | 13 | 14 | const createString = (name, age) => { 15 | return `Hi ${name}, you are ${age} years old.`; 16 | }; -------------------------------------------------------------------------------- /Chapter02/lambda3/hr.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | calculateHR: (age) => { 3 | return 220 - age; 4 | } 5 | } -------------------------------------------------------------------------------- /Chapter02/lambda3/index.js: -------------------------------------------------------------------------------- 1 | const HR = require('./hr'); 2 | 3 | exports.handler = async (event) => { 4 | // TODO implement 5 | console.log(event); 6 | let { 7 | name, 8 | age 9 | } = event; 10 | // same as => let name = event.name; let age = event.age 11 | 12 | return createString(name, age); 13 | }; 14 | 15 | 16 | const createString = (name, age) => { 17 | return `Hi ${name}, you are ${age} years old and have a maximum heart rate of ${HR.calculateHR(age)}.`; 18 | }; -------------------------------------------------------------------------------- /Chapter03/basicSkill/archive.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Hands-On-Chatbot-Development-with-Alexa-Skills-and-Amazon-Lex/a90bd11fcdb4c0c42b7d30094413e36106826d1f/Chapter03/basicSkill/archive.zip -------------------------------------------------------------------------------- /Chapter03/basicSkill/index.js: -------------------------------------------------------------------------------- 1 | const Alexa = require('ask-sdk-core'); 2 | 3 | const helloHandler = { 4 | canHandle(handlerInput) { 5 | return handlerInput.requestEnvelope.request.type === 'IntentRequest' && 6 | handlerInput.requestEnvelope.request.intent.name === 'hello'; 7 | }, 8 | handle(handlerInput) { 9 | const speechText = `Hello from Sam's new intent 3!`; 10 | 11 | return handlerInput.responseBuilder 12 | .speak(speechText) 13 | .getResponse(); 14 | } 15 | }; 16 | 17 | exports.handler = Alexa.SkillBuilders.custom() 18 | .addRequestHandlers( 19 | helloHandler) 20 | .lambda(); -------------------------------------------------------------------------------- /Chapter03/basicSkill/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "basicskill", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "ask-sdk-core": { 8 | "version": "2.0.7", 9 | "resolved": "https://registry.npmjs.org/ask-sdk-core/-/ask-sdk-core-2.0.7.tgz", 10 | "integrity": "sha512-L0YoF7ls0iUoo/WYDYj7uDjAFThYZSDjzF8YvLHIEZyzKVgrNNqxetRorUB+odDoctPWW7RV1xcep8F4p7c1jg==" 11 | }, 12 | "ask-sdk-model": { 13 | "version": "1.5.0", 14 | "resolved": "https://registry.npmjs.org/ask-sdk-model/-/ask-sdk-model-1.5.0.tgz", 15 | "integrity": "sha512-CpHtK17hmwDvHeNLEV+5QoudMTDibji9WuAA57lT0Evpygc0nrCdJtb7llfEsiomE6LKg/faZkqg4QSwVeGtoQ==" 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Chapter03/basicSkill/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "basicskill", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "ask-sdk-core": "^2.0.7", 13 | "ask-sdk-model": "^1.5.0" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Chapter03/car-data/fiat500.json: -------------------------------------------------------------------------------- 1 | { 2 | "make": "Fiat", 3 | "model": "500", 4 | "rrp": 10730.9, 5 | "fuelEcon": "60-74 mpg", 6 | "dimensions": "3.571 m L x 1.627 m W x 1.488 m H", 7 | "NCAPSafetyRating": "3 star", 8 | "cargo": 182 9 | } -------------------------------------------------------------------------------- /Chapter03/car-data/fordCmax.json: -------------------------------------------------------------------------------- 1 | { 2 | "make": "Ford", 3 | "model": "C-Max", 4 | "rrp": 21700, 5 | "fuelEcon": "55.4 mpg", 6 | "dimensions": "4.380 m L x 1.828 m W x 1.626 m H", 7 | "NCAPSafetyRating": "5 star", 8 | "cargo": 432 9 | } -------------------------------------------------------------------------------- /Chapter03/car-data/fordFiesta.json: -------------------------------------------------------------------------------- 1 | { 2 | "make": "Ford", 3 | "model": "Fiesta", 4 | "rrp": 13470, 5 | "fuelEcon": "49-88 mpg", 6 | "dimensions": "4.040-4.068 m L x 1.735 m W x 1.466-1.495 m H", 7 | "NCAPSafetyRating": "5 star", 8 | "cargo": 292 9 | } -------------------------------------------------------------------------------- /Chapter03/car-data/hyundaiI10.json: -------------------------------------------------------------------------------- 1 | { 2 | "make": "Hyundai", 3 | "model": "I10", 4 | "rrp": 8911, 5 | "fuelEcon": "48-71 mpg", 6 | "dimensions": "3.665 m L x 1.660 m W x 1.500 m H", 7 | "NCAPSafetyRating": "4 star", 8 | "cargo": 218 9 | } -------------------------------------------------------------------------------- /Chapter03/car-data/mercedesEClass.json: -------------------------------------------------------------------------------- 1 | { 2 | "make": "Mercedes-Benz", 3 | "model": "", 4 | "rrp": 35150, 5 | "fuelEcon": "32-66 mpg", 6 | "dimensions": "4.846-5.000 m L x 1.852-1.907 m W x 1.429-1.482 m H", 7 | "NCAPSafetyRating": "5 star", 8 | "cargo": 425 9 | } -------------------------------------------------------------------------------- /Chapter03/car-data/peugeot208.json: -------------------------------------------------------------------------------- 1 | { 2 | "make": "Peugeot", 3 | "model": "208", 4 | "rrp": 13774, 5 | "fuelEcon": "51-94 mpg", 6 | "dimensions": "3.973 m L x 1.739 m W x 1.450-1.460 m H", 7 | "NCAPSafetyRating": "5 star", 8 | "cargo": 285 9 | } -------------------------------------------------------------------------------- /Chapter03/car-data/scodaOctaviaAuto.json: -------------------------------------------------------------------------------- 1 | { 2 | "make": "Scoda", 3 | "model": "Octavia Auto", 4 | "rrp": 16910, 5 | "fuelEcon": "44-72 mpg", 6 | "dimensions": "4.667-4.689 m L x 1.814 m W x 1.448-1.495 m H", 7 | "NCAPSafetyRating": "5 star", 8 | "cargo": 590 9 | } -------------------------------------------------------------------------------- /Chapter03/car-data/vauxhallAstra.json: -------------------------------------------------------------------------------- 1 | { 2 | "make": "Vauxhall", 3 | "model": "Astra", 4 | "rrp": 16200, 5 | "fuelEcon": "44-79 mpg", 6 | "dimensions": "4.258 m L x 1.799 m W x 2.006 m H", 7 | "NCAPSafetyRating": "5 star", 8 | "cargo": 370 9 | } -------------------------------------------------------------------------------- /Chapter03/car-data/vauxhallInsignia.json: -------------------------------------------------------------------------------- 1 | { 2 | "make": "Vauxhall", 3 | "model": "Insignia", 4 | "rrp": 19330, 5 | "fuelEcon": "55.3 mpg", 6 | "dimensions": "4.842 m L x 2.084 m W x 1.498 m H", 7 | "NCAPSafetyRating": "5 star", 8 | "cargo": 530 9 | } -------------------------------------------------------------------------------- /Chapter03/car-data/vwGolf.json: -------------------------------------------------------------------------------- 1 | { 2 | "make": "Volkswagen", 3 | "model": "Golf S", 4 | "rrp": 17435, 5 | "fuelEcon": "59 mpg", 6 | "dimensions": "4.258 m L x 1.799 m W x 2.006 m H", 7 | "NCAPSafetyRating": "5 star", 8 | "cargo": 380 9 | } -------------------------------------------------------------------------------- /Chapter03/suggestCar/archive.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Hands-On-Chatbot-Development-with-Alexa-Skills-and-Amazon-Lex/a90bd11fcdb4c0c42b7d30094413e36106826d1f/Chapter03/suggestCar/archive.zip -------------------------------------------------------------------------------- /Chapter03/suggestCar/index.js: -------------------------------------------------------------------------------- 1 | const Alexa = require('ask-sdk'); 2 | const AWS = require('aws-sdk'); 3 | const s3 = new AWS.S3(); 4 | 5 | const cars = [ 6 | { name: 'fiat500', size: 'small', cost: 'luxury', doors: 3, gears: '' }, 7 | { name: 'fordFiesta', size: 'small', cost: 'luxury', doors: 5, gears: '' }, 8 | { name: 'hyundaiI20', size: 'small', cost: 'value', doors: 3, gears: '' }, 9 | { name: 'peugeot208', size: 'small', cost: 'value', doors: 5, gears: '' }, 10 | 11 | { name: 'vauxhallAstra', size: 'medium', cost: 'value', doors: 5, gears: '' }, 12 | { name: 'vwGolf', size: 'medium', cost: 'luxury', doors: 5, gears: '' }, 13 | 14 | { name: 'scodaOctaviaAuto', size: 'large', cost: 'value', doors: 5, gears: 'automatic' }, 15 | { name: 'fordCmax', size: 'large', cost: 'value', doors: 5, gears: 'manual' }, 16 | { name: 'mercedesEClass', size: 'large', cost: 'luxury', doors: 5, gears: 'automatic' }, 17 | { name: 'Vauxhall Insignia', size: 'large', cost: 'luxury', doors: 5, gears: 'manual' } 18 | ]; 19 | 20 | const LaunchRequestHandler = { 21 | canHandle(handlerInput) { 22 | return handlerInput.requestEnvelope.request.type === 'LaunchRequest'; 23 | }, 24 | handle(handlerInput) { 25 | const speechText = `Hi there, I'm Car Helper. You can ask me to suggest a car for you.`; 26 | 27 | return handlerInput.responseBuilder 28 | .speak(speechText) 29 | .reprompt(speechText) 30 | .getResponse(); 31 | } 32 | }; 33 | 34 | const WhichCarHandler = { 35 | canHandle(handlerInput) { 36 | return handlerInput.requestEnvelope.request.type === 'IntentRequest' && 37 | handlerInput.requestEnvelope.request.intent.name === 'whichCar'; 38 | }, 39 | async handle(handlerInput) { 40 | const slots = handlerInput.requestEnvelope.request.intent.slots; 41 | const { size, cost, gears, doors } = slots; 42 | 43 | if (!size || !(size.value === 'large' || size.value === 'medium' || size.value == 'small')) { 44 | const slotToElicit = 'size'; 45 | const speechOutput = 'What size car do you want? Please say either small, medium or large.'; 46 | return handlerInput.responseBuilder 47 | .speak(speechOutput) 48 | .addElicitSlotDirective(slotToElicit) 49 | .getResponse(); 50 | } 51 | 52 | if (!cost || !(cost.value === 'luxury' || cost.value === 'value')) { 53 | const slotToElicit = 'cost'; 54 | const speechOutput = 'Are you looking for a luxury or value car?'; 55 | return handlerInput.responseBuilder 56 | .speak(speechOutput) 57 | .addElicitSlotDirective(slotToElicit) 58 | .getResponse(); 59 | } 60 | 61 | if (size.value === 'large' && (!gears || !(gears.value === 'automatic' || gears.value === 'manual'))) { 62 | // missing or incorrect gears 63 | const slotToElicit = 'gears'; 64 | const speechOutput = 'Do you want an automatic or a manual transmission?'; 65 | return handlerInput.responseBuilder 66 | .speak(speechOutput) 67 | .addElicitSlotDirective(slotToElicit) 68 | .getResponse(); 69 | } 70 | 71 | if (size.value === 'small' && (!doors || !(doors.value == 3 || doors.value == 5))) { 72 | // missing or incorrect doors 73 | const slotToElicit = 'doors'; 74 | const speechOutput = 'Do you want 3 or 5 doors?'; 75 | return handlerInput.responseBuilder 76 | .speak(speechOutput) 77 | .addElicitSlotDirective(slotToElicit) 78 | .getResponse(); 79 | } 80 | 81 | // find the ideal car 82 | 83 | let chosenCar = cars.filter(car => { 84 | return (car.size === size.value && car.cost === cost.value && 85 | ((gears && gears.value) ? car.gears === gears.value : true) && 86 | ((doors && doors.value) ? car.doors == doors.value : true)); 87 | }); 88 | console.log('chosenCar', chosenCar); 89 | 90 | if (chosenCar.length !== 1) { 91 | const speechOutput = `Unfortunately I couldn't find the best car for you. You can say "suggest a car" if you want to try again.`; 92 | return handlerInput.responseBuilder 93 | .speak(speechOutput) 94 | .getResponse(); 95 | } 96 | 97 | var params = { 98 | Bucket: 'car-data-sam', 99 | Key: `${chosenCar[0].name}.json` 100 | }; 101 | 102 | return new Promise((resolve, reject) => { 103 | s3.getObject(params, function(err, data) { 104 | if (err) { // an error occurred 105 | console.log(err, err.stack); 106 | reject(handleS3Error(handlerInput)); 107 | } else { // successful response 108 | console.log(data); 109 | resolve(handleS3Data(handlerInput, data)); 110 | } 111 | }); 112 | }) 113 | } 114 | }; 115 | 116 | const handleS3Error = handlerInput => { 117 | const speechOutput = `I've had a problem finding the perfect car for you.` 118 | return handlerInput.responseBuilder 119 | .speak(speechOutput) 120 | .getResponse(); 121 | }; 122 | 123 | 124 | function handleS3Data(handlerInput, data) { 125 | console.log('body= ', JSON.parse(data.Body.toString())); 126 | let { make, model, rrp, fuelEcon, dimensions, NCAPSafetyRating, cargo } = JSON.parse(data.Body.toString()); 127 | let speechOutput = `I think that a ${make} ${model} would be a good car for you. 128 | They're available from ${rrp} pounds, get ${fuelEcon} and have a ${cargo} litre boot.`; 129 | console.log(speechOutput); 130 | return handlerInput.responseBuilder 131 | .speak(speechOutput) 132 | .getResponse(); 133 | } 134 | 135 | exports.handler = Alexa.SkillBuilders.custom() 136 | .addRequestHandlers( 137 | LaunchRequestHandler, 138 | WhichCarHandler 139 | ) 140 | .lambda(); -------------------------------------------------------------------------------- /Chapter03/suggestCar/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "suggestcar", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "ask-sdk": { 8 | "version": "2.0.7", 9 | "resolved": "https://registry.npmjs.org/ask-sdk/-/ask-sdk-2.0.7.tgz", 10 | "integrity": "sha512-fJDjDv1BqTvfiwHKOgPYuQbi0HCfe320Po1S6qqhdgHN/ZHuolHRYIEe8i3O1sk/qObCsrwOD9xh0l10E48Spw==", 11 | "requires": { 12 | "ask-sdk-core": "2.0.7", 13 | "ask-sdk-dynamodb-persistence-adapter": "2.0.7", 14 | "ask-sdk-model": "1.5.0" 15 | } 16 | }, 17 | "ask-sdk-core": { 18 | "version": "2.0.7", 19 | "resolved": "https://registry.npmjs.org/ask-sdk-core/-/ask-sdk-core-2.0.7.tgz", 20 | "integrity": "sha512-L0YoF7ls0iUoo/WYDYj7uDjAFThYZSDjzF8YvLHIEZyzKVgrNNqxetRorUB+odDoctPWW7RV1xcep8F4p7c1jg==" 21 | }, 22 | "ask-sdk-dynamodb-persistence-adapter": { 23 | "version": "2.0.7", 24 | "resolved": "https://registry.npmjs.org/ask-sdk-dynamodb-persistence-adapter/-/ask-sdk-dynamodb-persistence-adapter-2.0.7.tgz", 25 | "integrity": "sha512-RHIOOAfsIwZy7hUtENPF12l1NldB8+rRZ3Jk4I9efpFFZSmXOM510Qho3Lt3OZCH9qTDRp1QWrQvn3ki7fdM8A==", 26 | "requires": { 27 | "aws-sdk": "2.311.0" 28 | } 29 | }, 30 | "ask-sdk-model": { 31 | "version": "1.5.0", 32 | "resolved": "https://registry.npmjs.org/ask-sdk-model/-/ask-sdk-model-1.5.0.tgz", 33 | "integrity": "sha512-CpHtK17hmwDvHeNLEV+5QoudMTDibji9WuAA57lT0Evpygc0nrCdJtb7llfEsiomE6LKg/faZkqg4QSwVeGtoQ==" 34 | }, 35 | "aws-sdk": { 36 | "version": "2.311.0", 37 | "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.311.0.tgz", 38 | "integrity": "sha512-OnIvCu3146CLCy9uEf9jj9PlIpKz5Notz6EDV0FQFMdE7QqKCBUFVgfS+lqQCiKOTMTDx+z+9MJmNLUMyGqIjA==", 39 | "requires": { 40 | "buffer": "4.9.1", 41 | "events": "1.1.1", 42 | "ieee754": "1.1.8", 43 | "jmespath": "0.15.0", 44 | "querystring": "0.2.0", 45 | "sax": "1.2.1", 46 | "url": "0.10.3", 47 | "uuid": "3.1.0", 48 | "xml2js": "0.4.19" 49 | } 50 | }, 51 | "base64-js": { 52 | "version": "1.3.0", 53 | "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.0.tgz", 54 | "integrity": "sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw==" 55 | }, 56 | "buffer": { 57 | "version": "4.9.1", 58 | "resolved": "http://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", 59 | "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", 60 | "requires": { 61 | "base64-js": "1.3.0", 62 | "ieee754": "1.1.8", 63 | "isarray": "1.0.0" 64 | } 65 | }, 66 | "events": { 67 | "version": "1.1.1", 68 | "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", 69 | "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=" 70 | }, 71 | "ieee754": { 72 | "version": "1.1.8", 73 | "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.8.tgz", 74 | "integrity": "sha1-vjPUCsEO8ZJnAfbwii2G+/0a0+Q=" 75 | }, 76 | "isarray": { 77 | "version": "1.0.0", 78 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 79 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" 80 | }, 81 | "jmespath": { 82 | "version": "0.15.0", 83 | "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz", 84 | "integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc=" 85 | }, 86 | "punycode": { 87 | "version": "1.3.2", 88 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", 89 | "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" 90 | }, 91 | "querystring": { 92 | "version": "0.2.0", 93 | "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", 94 | "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" 95 | }, 96 | "sax": { 97 | "version": "1.2.1", 98 | "resolved": "http://registry.npmjs.org/sax/-/sax-1.2.1.tgz", 99 | "integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o=" 100 | }, 101 | "url": { 102 | "version": "0.10.3", 103 | "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", 104 | "integrity": "sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=", 105 | "requires": { 106 | "punycode": "1.3.2", 107 | "querystring": "0.2.0" 108 | } 109 | }, 110 | "uuid": { 111 | "version": "3.1.0", 112 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz", 113 | "integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g==" 114 | }, 115 | "xml2js": { 116 | "version": "0.4.19", 117 | "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", 118 | "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", 119 | "requires": { 120 | "sax": "1.2.1", 121 | "xmlbuilder": "9.0.7" 122 | } 123 | }, 124 | "xmlbuilder": { 125 | "version": "9.0.7", 126 | "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", 127 | "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=" 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /Chapter03/suggestCar/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "suggestcar", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "ask-sdk": "^2.0.7" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Chapter04/boilerplate Lambda/index.js: -------------------------------------------------------------------------------- 1 | const Alexa = require('ask-sdk'); 2 | 3 | const LaunchRequestHandler = { 4 | canHandle(handlerInput) { 5 | return handlerInput.requestEnvelope.request.type === 'LaunchRequest'; 6 | }, 7 | handle(handlerInput) { 8 | const speechText = ``; 9 | 10 | return handlerInput.responseBuilder 11 | .speak(speechText) 12 | .reprompt(speechText) 13 | .getResponse(); 14 | } 15 | }; 16 | 17 | const ErrorHandler = { 18 | canHandle() { 19 | return true; 20 | }, 21 | handle(handlerInput, error) { 22 | console.log(`Error handled: ${error.message}`); 23 | 24 | return handlerInput.responseBuilder 25 | .speak(`Sorry, I can't understand the command. Please say again.`) 26 | .reprompt(`Sorry, I can't understand the command. Please say again.`) 27 | .getResponse(); 28 | }, 29 | }; 30 | 31 | 32 | 33 | exports.handler = Alexa.SkillBuilders.custom() 34 | .addRequestHandlers( 35 | LaunchRequestHandler 36 | ) 37 | .addErrorHandler(ErrorHandler) 38 | .lambda(); -------------------------------------------------------------------------------- /Chapter04/boilerplate Lambda/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "requires": true, 3 | "lockfileVersion": 1, 4 | "dependencies": { 5 | "ask-sdk": { 6 | "version": "2.0.7", 7 | "resolved": "https://registry.npmjs.org/ask-sdk/-/ask-sdk-2.0.7.tgz", 8 | "integrity": "sha512-fJDjDv1BqTvfiwHKOgPYuQbi0HCfe320Po1S6qqhdgHN/ZHuolHRYIEe8i3O1sk/qObCsrwOD9xh0l10E48Spw==", 9 | "requires": { 10 | "ask-sdk-core": "2.0.7", 11 | "ask-sdk-dynamodb-persistence-adapter": "2.0.7", 12 | "ask-sdk-model": "1.5.0" 13 | } 14 | }, 15 | "ask-sdk-core": { 16 | "version": "2.0.7", 17 | "resolved": "https://registry.npmjs.org/ask-sdk-core/-/ask-sdk-core-2.0.7.tgz", 18 | "integrity": "sha512-L0YoF7ls0iUoo/WYDYj7uDjAFThYZSDjzF8YvLHIEZyzKVgrNNqxetRorUB+odDoctPWW7RV1xcep8F4p7c1jg==" 19 | }, 20 | "ask-sdk-dynamodb-persistence-adapter": { 21 | "version": "2.0.7", 22 | "resolved": "https://registry.npmjs.org/ask-sdk-dynamodb-persistence-adapter/-/ask-sdk-dynamodb-persistence-adapter-2.0.7.tgz", 23 | "integrity": "sha512-RHIOOAfsIwZy7hUtENPF12l1NldB8+rRZ3Jk4I9efpFFZSmXOM510Qho3Lt3OZCH9qTDRp1QWrQvn3ki7fdM8A==", 24 | "requires": { 25 | "aws-sdk": "2.311.0" 26 | } 27 | }, 28 | "ask-sdk-model": { 29 | "version": "1.5.0", 30 | "resolved": "https://registry.npmjs.org/ask-sdk-model/-/ask-sdk-model-1.5.0.tgz", 31 | "integrity": "sha512-CpHtK17hmwDvHeNLEV+5QoudMTDibji9WuAA57lT0Evpygc0nrCdJtb7llfEsiomE6LKg/faZkqg4QSwVeGtoQ==" 32 | }, 33 | "aws-sdk": { 34 | "version": "2.311.0", 35 | "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.311.0.tgz", 36 | "integrity": "sha512-OnIvCu3146CLCy9uEf9jj9PlIpKz5Notz6EDV0FQFMdE7QqKCBUFVgfS+lqQCiKOTMTDx+z+9MJmNLUMyGqIjA==", 37 | "requires": { 38 | "buffer": "4.9.1", 39 | "events": "1.1.1", 40 | "ieee754": "1.1.8", 41 | "jmespath": "0.15.0", 42 | "querystring": "0.2.0", 43 | "sax": "1.2.1", 44 | "url": "0.10.3", 45 | "uuid": "3.1.0", 46 | "xml2js": "0.4.19" 47 | } 48 | }, 49 | "base64-js": { 50 | "version": "1.3.0", 51 | "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.0.tgz", 52 | "integrity": "sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw==" 53 | }, 54 | "buffer": { 55 | "version": "4.9.1", 56 | "resolved": "http://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", 57 | "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", 58 | "requires": { 59 | "base64-js": "1.3.0", 60 | "ieee754": "1.1.8", 61 | "isarray": "1.0.0" 62 | } 63 | }, 64 | "events": { 65 | "version": "1.1.1", 66 | "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", 67 | "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=" 68 | }, 69 | "ieee754": { 70 | "version": "1.1.8", 71 | "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.8.tgz", 72 | "integrity": "sha1-vjPUCsEO8ZJnAfbwii2G+/0a0+Q=" 73 | }, 74 | "isarray": { 75 | "version": "1.0.0", 76 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 77 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" 78 | }, 79 | "jmespath": { 80 | "version": "0.15.0", 81 | "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz", 82 | "integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc=" 83 | }, 84 | "punycode": { 85 | "version": "1.3.2", 86 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", 87 | "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" 88 | }, 89 | "querystring": { 90 | "version": "0.2.0", 91 | "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", 92 | "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" 93 | }, 94 | "sax": { 95 | "version": "1.2.1", 96 | "resolved": "http://registry.npmjs.org/sax/-/sax-1.2.1.tgz", 97 | "integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o=" 98 | }, 99 | "url": { 100 | "version": "0.10.3", 101 | "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", 102 | "integrity": "sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=", 103 | "requires": { 104 | "punycode": "1.3.2", 105 | "querystring": "0.2.0" 106 | } 107 | }, 108 | "uuid": { 109 | "version": "3.1.0", 110 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz", 111 | "integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g==" 112 | }, 113 | "xml2js": { 114 | "version": "0.4.19", 115 | "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", 116 | "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", 117 | "requires": { 118 | "sax": "1.2.1", 119 | "xmlbuilder": "9.0.7" 120 | } 121 | }, 122 | "xmlbuilder": { 123 | "version": "9.0.7", 124 | "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", 125 | "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=" 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /Chapter04/boilerplate Lambda/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "", 3 | "version": "", 4 | "dependencies": { 5 | "ask-sdk": "^2.0.7" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Chapter04/weatherGods-2/archive.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Hands-On-Chatbot-Development-with-Alexa-Skills-and-Amazon-Lex/a90bd11fcdb4c0c42b7d30094413e36106826d1f/Chapter04/weatherGods-2/archive.zip -------------------------------------------------------------------------------- /Chapter04/weatherGods-2/index.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios'); 2 | const moment = require('moment'); 3 | const Alexa = require('ask-sdk'); 4 | 5 | const LaunchRequestHandler = { 6 | canHandle(handlerInput) { 7 | return handlerInput.requestEnvelope.request.type === 'LaunchRequest'; 8 | }, 9 | handle(handlerInput) { 10 | const speechText = 'You may ask the weather gods about the weather in your city or for a weather forecast'; 11 | 12 | return handlerInput.responseBuilder 13 | .speak(speechText) 14 | .reprompt(speechText) 15 | .getResponse(); 16 | } 17 | }; 18 | 19 | const ErrorHandler = { 20 | canHandle() { 21 | return true; 22 | }, 23 | handle(handlerInput, error) { 24 | console.log(`Error handled: ${error.message}`); 25 | 26 | return handlerInput.responseBuilder 27 | .speak(`Sorry, I can't understand the command. Please say again.`) 28 | .getResponse(); 29 | }, 30 | }; 31 | 32 | let jokes = [ 33 | `Where do snowmen keep their money? In a snow bank `, 34 | `As we waited for a bus in the frosty weather, the woman next to me mentioned that she makes a lot of mistakes when texting in the cold. I nodded knowingly. It’s the early signs of typothermia `, 35 | `Don’t knock the weather. If it didn’t change once in a while, nine tenths of the people couldn’t start a conversation` 36 | ]; 37 | 38 | const JokeHandler = { 39 | canHandle(handlerInput) { 40 | return handlerInput.requestEnvelope.request.type === 'IntentRequest' && 41 | handlerInput.requestEnvelope.request.intent.name === 'tellAJoke'; 42 | }, 43 | async handle(handlerInput) { 44 | let random = Math.floor(Math.random() * 3); 45 | let joke = jokes[random]; 46 | return handlerInput.responseBuilder 47 | .speak(joke) 48 | .withShouldEndSession(false) 49 | .getResponse(); 50 | } 51 | }; 52 | 53 | const GetWeatherHandler = { 54 | canHandle(handlerInput) { 55 | return handlerInput.requestEnvelope.request.type === 'IntentRequest' && 56 | handlerInput.requestEnvelope.request.intent.name === 'getWeather'; 57 | }, 58 | async handle(handlerInput) { 59 | console.log('start get weather'); 60 | const { slots } = handlerInput.requestEnvelope.request.intent; 61 | let { location, date } = slots; 62 | let sessionAttributes = handlerInput.attributesManager.getSessionAttributes(); 63 | console.log('sessionAttributes', sessionAttributes); 64 | location = location.value || sessionAttributes.location || null; 65 | date = date.value || sessionAttributes.date || null; 66 | sessionAttributes = { location, date }; 67 | 68 | if (!location) { 69 | console.log('get location'); 70 | let slotToElicit = 'location'; 71 | let speechOutput = 'Where do you want to know the weather for?'; 72 | return handlerInput.responseBuilder 73 | .speak(speechOutput) 74 | .addElicitSlotDirective(slotToElicit) 75 | .getResponse(); 76 | } 77 | if (!date) { 78 | date = Date.now() 79 | } 80 | 81 | let isToday = moment(date).isSame(Date.now(), 'day'); 82 | 83 | try { 84 | if (isToday) { 85 | console.log('isToday'); 86 | let [error, response] = await to(axios.get(`https://api.openweathermap.org/data/2.5/weather?q=${location},us&appid=${process.env.API_KEY}`)); 87 | let { data: weatherResponse } = response; 88 | if (error) { 89 | console.log('error getting weather', error.response); 90 | let errorSpeech = `We couldn't get the weather for ${location} but you can try again later`; 91 | return handlerInput.responseBuilder 92 | .speak(errorSpeech) 93 | .getResponse(); 94 | } 95 | let { weather, main: { temp, humidity } } = weatherResponse; 96 | console.log('got weather response', weatherResponse); 97 | let weatherString = formatWeatherString(weather); 98 | let formattedTemp = tempC(temp); 99 | // let formattedTemp = tempF(temp); 100 | let speechText = `The weather in ${location} has ${weatherString} with a temperature of ${formattedTemp} and a humidity of ${humidity} percent`; 101 | handlerInput.attributesManager.setSessionAttributes(sessionAttributes); 102 | return handlerInput.responseBuilder 103 | .speak(speechText) 104 | .withShouldEndSession(false) 105 | .getResponse(); 106 | } else { 107 | let { data: forecastResponse } = await axios.get(`https://api.openweathermap.org/data/2.5/forecast?q=${location},us&appid=${process.env.API_KEY}`); 108 | let { list } = forecastResponse; 109 | // reduce the data we keep 110 | let usefulForecast = list.map(weatherPeriod => { 111 | let { dt_txt, weather, main: { temp, humidity } } = weatherPeriod; 112 | return { dt_txt, weather, temp, humidity } 113 | }); 114 | // reduce to 9am and 6pm forecasts only 115 | let reducedForecast = usefulForecast.filter(weatherPeriod => { 116 | let time = weatherPeriod.dt_txt.slice(-8); 117 | return time === '09:00:00' || time === '18:00:00'; 118 | }); 119 | console.log('got forecaset and reduced it ', reducedForecast); 120 | // reduce to the day the user asked about 121 | let dayForecast = reducedForecast.filter(forecast => { 122 | return moment(date).isSame(forecast.dt_txt, 'day'); 123 | }); 124 | 125 | let weatherString = dayForecast.map(forecast => formatWeatherString(forecast.weather)); 126 | let formattedTemp = dayForecast.map(forecast => tempC(forecast.temp)); 127 | let humidity = dayForecast.map(forecast => forecast.humidity); 128 | let speechText = `The weather in ${location} ${date} will have ${weatherString[0]} with a temperature of ${formattedTemp[0]} and a humidity of ${humidity[0]} percent, whilst in the afternoon it will have ${weatherString[1]} with a temperature of ${formattedTemp[1]} and a humidity of ${humidity[1]} percent`; 129 | handlerInput.attributesManager.setSessionAttributes(sessionAttributes); 130 | return handlerInput.responseBuilder 131 | .speak(speechText) 132 | .withShouldEndSession(false) 133 | .getResponse(); 134 | } 135 | } catch (err) { 136 | console.log('err', err) 137 | return handlerInput.responseBuilder 138 | .speak(`My powers are weak and I couldn't get the weather right now.`) 139 | .getResponse(); 140 | } 141 | } 142 | } 143 | 144 | const to = promise => promise.then(res => [null, res]).catch(err => [err, null]); 145 | 146 | const tempC = temp => Math.floor(temp - 273.15) + ' degrees Celsius '; 147 | 148 | const tempF = temp => Math.floor(9 / 5 * (temp - 273) + 32) + ' Fahrenheit'; 149 | 150 | const formatWeatherString = weather => { 151 | if (weather.length === 1) return weather[0].description; 152 | return weather.slice(0, -1).map(item => item.description).join(', ') + ' and ' + weather.slice(-1)[0].description; 153 | }; 154 | 155 | 156 | exports.handler = Alexa.SkillBuilders.custom() 157 | .addRequestHandlers( 158 | LaunchRequestHandler, 159 | GetWeatherHandler, 160 | JokeHandler 161 | ) 162 | .addErrorHandlers(ErrorHandler) 163 | .lambda(); -------------------------------------------------------------------------------- /Chapter04/weatherGods-2/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "weathergods-2", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "ask-sdk": { 8 | "version": "2.0.9", 9 | "resolved": "https://registry.npmjs.org/ask-sdk/-/ask-sdk-2.0.9.tgz", 10 | "integrity": "sha512-jHg+/dbfh2+PEjPiGdMffSFcabvy4Hj0Lfms+8sCXdfyEw0qtDGEFpi8xWsmAvRUNPSBJ9zzS268fZf3mbj/uw==", 11 | "requires": { 12 | "ask-sdk-core": "2.0.9", 13 | "ask-sdk-dynamodb-persistence-adapter": "2.0.9", 14 | "ask-sdk-model": "1.5.0" 15 | } 16 | }, 17 | "ask-sdk-core": { 18 | "version": "2.0.9", 19 | "resolved": "https://registry.npmjs.org/ask-sdk-core/-/ask-sdk-core-2.0.9.tgz", 20 | "integrity": "sha512-d74XPj2i7CpKGiznHJpGUwNYG7Rdvgw1j5aj+MPEYHrSxR5b3cBx7gnIy/5Tbfu21HW2RoLTi7WRlxCQfPzurg==" 21 | }, 22 | "ask-sdk-dynamodb-persistence-adapter": { 23 | "version": "2.0.9", 24 | "resolved": "https://registry.npmjs.org/ask-sdk-dynamodb-persistence-adapter/-/ask-sdk-dynamodb-persistence-adapter-2.0.9.tgz", 25 | "integrity": "sha512-2UWE+kuupgE/2rfF5g+UAIqI3UqLKGlMYALDW6pQCePUgKFhjRAVYvOdDaES5TZDF4kxb0k3Aka/AYpDDO17MQ==", 26 | "requires": { 27 | "aws-sdk": "2.312.0" 28 | } 29 | }, 30 | "ask-sdk-model": { 31 | "version": "1.5.0", 32 | "resolved": "https://registry.npmjs.org/ask-sdk-model/-/ask-sdk-model-1.5.0.tgz", 33 | "integrity": "sha512-CpHtK17hmwDvHeNLEV+5QoudMTDibji9WuAA57lT0Evpygc0nrCdJtb7llfEsiomE6LKg/faZkqg4QSwVeGtoQ==" 34 | }, 35 | "aws-sdk": { 36 | "version": "2.312.0", 37 | "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.312.0.tgz", 38 | "integrity": "sha512-Nw8a/wtqt13zxq+fdRFeNdGzeiJaG6rIdJoonh4JafL69uE9QlNRgHz+oO/coVw8apS14qVkS/nSSX+6nuMSQw==", 39 | "requires": { 40 | "buffer": "4.9.1", 41 | "events": "1.1.1", 42 | "ieee754": "1.1.8", 43 | "jmespath": "0.15.0", 44 | "querystring": "0.2.0", 45 | "sax": "1.2.1", 46 | "url": "0.10.3", 47 | "uuid": "3.1.0", 48 | "xml2js": "0.4.19" 49 | } 50 | }, 51 | "axios": { 52 | "version": "0.18.0", 53 | "resolved": "http://registry.npmjs.org/axios/-/axios-0.18.0.tgz", 54 | "integrity": "sha1-MtU+SFHv3AoRmTts0AB4nXDAUQI=", 55 | "requires": { 56 | "follow-redirects": "1.5.7", 57 | "is-buffer": "1.1.6" 58 | } 59 | }, 60 | "base64-js": { 61 | "version": "1.3.0", 62 | "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.0.tgz", 63 | "integrity": "sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw==" 64 | }, 65 | "buffer": { 66 | "version": "4.9.1", 67 | "resolved": "http://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", 68 | "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", 69 | "requires": { 70 | "base64-js": "1.3.0", 71 | "ieee754": "1.1.8", 72 | "isarray": "1.0.0" 73 | } 74 | }, 75 | "debug": { 76 | "version": "3.2.1", 77 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.1.tgz", 78 | "integrity": "sha512-P5CJ6AGKlnMM3Rd1V4xmQ1SNn5VQ/4UoIiVmDzJxliKCeG1ANIj6ThcWWsefqZ4WdzGdmhG3WdeKrcjx9eNUYA==", 79 | "requires": { 80 | "ms": "2.1.1" 81 | } 82 | }, 83 | "events": { 84 | "version": "1.1.1", 85 | "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", 86 | "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=" 87 | }, 88 | "follow-redirects": { 89 | "version": "1.5.7", 90 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.7.tgz", 91 | "integrity": "sha512-NONJVIFiX7Z8k2WxfqBjtwqMifx7X42ORLFrOZ2LTKGj71G3C0kfdyTqGqr8fx5zSX6Foo/D95dgGWbPUiwnew==", 92 | "requires": { 93 | "debug": "3.2.1" 94 | } 95 | }, 96 | "ieee754": { 97 | "version": "1.1.8", 98 | "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.8.tgz", 99 | "integrity": "sha1-vjPUCsEO8ZJnAfbwii2G+/0a0+Q=" 100 | }, 101 | "is-buffer": { 102 | "version": "1.1.6", 103 | "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", 104 | "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" 105 | }, 106 | "isarray": { 107 | "version": "1.0.0", 108 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 109 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" 110 | }, 111 | "jmespath": { 112 | "version": "0.15.0", 113 | "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz", 114 | "integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc=" 115 | }, 116 | "moment": { 117 | "version": "2.22.2", 118 | "resolved": "https://registry.npmjs.org/moment/-/moment-2.22.2.tgz", 119 | "integrity": "sha1-PCV/mDn8DpP/UxSWMiOeuQeD/2Y=" 120 | }, 121 | "ms": { 122 | "version": "2.1.1", 123 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", 124 | "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" 125 | }, 126 | "punycode": { 127 | "version": "1.3.2", 128 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", 129 | "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" 130 | }, 131 | "querystring": { 132 | "version": "0.2.0", 133 | "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", 134 | "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" 135 | }, 136 | "sax": { 137 | "version": "1.2.1", 138 | "resolved": "http://registry.npmjs.org/sax/-/sax-1.2.1.tgz", 139 | "integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o=" 140 | }, 141 | "url": { 142 | "version": "0.10.3", 143 | "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", 144 | "integrity": "sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=", 145 | "requires": { 146 | "punycode": "1.3.2", 147 | "querystring": "0.2.0" 148 | } 149 | }, 150 | "uuid": { 151 | "version": "3.1.0", 152 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz", 153 | "integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g==" 154 | }, 155 | "xml2js": { 156 | "version": "0.4.19", 157 | "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", 158 | "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", 159 | "requires": { 160 | "sax": "1.2.1", 161 | "xmlbuilder": "9.0.7" 162 | } 163 | }, 164 | "xmlbuilder": { 165 | "version": "9.0.7", 166 | "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", 167 | "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=" 168 | } 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /Chapter04/weatherGods-2/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "weathergods-2", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "ask-sdk": "^2.0.7", 13 | "axios": "^0.18.0", 14 | "moment": "^2.22.2" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Chapter04/weatherGods/archive.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Hands-On-Chatbot-Development-with-Alexa-Skills-and-Amazon-Lex/a90bd11fcdb4c0c42b7d30094413e36106826d1f/Chapter04/weatherGods/archive.zip -------------------------------------------------------------------------------- /Chapter04/weatherGods/index.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios'); 2 | const moment = require('moment'); 3 | const Alexa = require('ask-sdk'); 4 | 5 | const LaunchRequestHandler = { 6 | canHandle(handlerInput) { 7 | return handlerInput.requestEnvelope.request.type === 'LaunchRequest'; 8 | }, 9 | handle(handlerInput) { 10 | const speechText = 'You may ask the weather gods about the weather in your city or for a weather forecast'; 11 | 12 | return handlerInput.responseBuilder 13 | .speak(speechText) 14 | .reprompt(speechText) 15 | .getResponse(); 16 | } 17 | }; 18 | 19 | const GetWeatherHandler = { 20 | canHandle(handlerInput) { 21 | return handlerInput.requestEnvelope.request.type === 'IntentRequest' && 22 | handlerInput.requestEnvelope.request.intent.name === 'getWeather'; 23 | }, 24 | async handle(handlerInput) { 25 | console.log('start get weather'); 26 | const { slots } = handlerInput.requestEnvelope.request.intent; 27 | let { location, date } = slots; 28 | location = location.value || null; 29 | date = date.value || null; 30 | 31 | if (!location) { 32 | console.log('get location'); 33 | let slotToElicit = 'location'; 34 | let speechOutput = 'Where do you want to know the weather for?'; 35 | return handlerInput.responseBuilder 36 | .speak(speechOutput) 37 | .addElicitSlotDirective(slotToElicit) 38 | .getResponse(); 39 | } 40 | if (!date) { 41 | date = Date.now() 42 | } 43 | 44 | let isToday = moment(date).isSame(Date.now(), 'day'); 45 | 46 | if (isToday) { 47 | console.log('isToday'); 48 | let { data: weatherResponse } = await axios.get(`https://api.openweathermap.org/data/2.5/weather?q=${location},us&appid=${process.env.API_KEY}`); 49 | let { weather, main: { temp, humidity } } = weatherResponse; 50 | console.log('got weather response', weatherResponse); 51 | let weatherString = formatWeatherString(weather); 52 | let formattedTemp = tempC(temp); 53 | // let formattedTemp = tempF(temp); 54 | let speechText = `The weather in ${location} has ${weatherString} with a temperature of ${formattedTemp} and a humidity of ${humidity} percent`; 55 | return handlerInput.responseBuilder 56 | .speak(speechText) 57 | .getResponse(); 58 | } else { 59 | let { data: forecastResponse } = await axios.get(`https://api.openweathermap.org/data/2.5/forecast?q=${location},us&appid=${process.env.API_KEY}`); 60 | let { list } = forecastResponse; 61 | // reduce the data we keep 62 | let usefulForecast = list.map(weatherPeriod => { 63 | let { dt_txt, weather, main: { temp, humidity } } = weatherPeriod; 64 | return { dt_txt, weather, temp, humidity } 65 | }); 66 | // reduce to 9am and 6pm forecasts only 67 | let reducedForecast = usefulForecast.filter(weatherPeriod => { 68 | let time = weatherPeriod.dt_txt.slice(-8); 69 | return time === '09:00:00' || time === '18:00:00'; 70 | }); 71 | console.log('got forecaset and reduced it ', reducedForecast); 72 | // reduce to the day the user asked about 73 | let dayForecast = reducedForecast.filter(forecast => { 74 | return moment(date).isSame(forecast.dt_txt, 'day'); 75 | }); 76 | 77 | let weatherString = dayForecast.map(forecast => formatWeatherString(forecast.weather)); 78 | let formattedTemp = dayForecast.map(forecast => tempC(forecast.temp)); 79 | let humidity = dayForecast.map(forecast => forecast.humidity); 80 | let speechText = `The weather in ${location} ${date} will have ${weatherString[0]} with a temperature of ${formattedTemp[0]} and a humidity of ${humidity[0]} percent, whilst in the afternoon it will have ${weatherString[1]} with a temperature of ${formattedTemp[1]} and a humidity of ${humidity[1]} percent`; 81 | return handlerInput.responseBuilder 82 | .speak(speechText) 83 | .getResponse(); 84 | } 85 | } 86 | } 87 | 88 | const tempC = temp => Math.floor(temp - 273.15) + ' degrees Celsius '; 89 | 90 | const tempF = temp => Math.floor(9 / 5 * (temp - 273) + 32) + ' Fahrenheit'; 91 | 92 | const formatWeatherString = weather => { 93 | if (weather.length === 1) return weather[0].description; 94 | return weather.slice(0, -1).map(item => item.description).join(', ') + ' and ' + weather.slice(-1)[0].description; 95 | }; 96 | 97 | 98 | exports.handler = Alexa.SkillBuilders.custom() 99 | .addRequestHandlers( 100 | LaunchRequestHandler, 101 | GetWeatherHandler 102 | ) 103 | .lambda(); -------------------------------------------------------------------------------- /Chapter04/weatherGods/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "weathergods", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "ask-sdk": { 8 | "version": "2.0.7", 9 | "resolved": "https://registry.npmjs.org/ask-sdk/-/ask-sdk-2.0.7.tgz", 10 | "integrity": "sha512-fJDjDv1BqTvfiwHKOgPYuQbi0HCfe320Po1S6qqhdgHN/ZHuolHRYIEe8i3O1sk/qObCsrwOD9xh0l10E48Spw==", 11 | "requires": { 12 | "ask-sdk-core": "2.0.7", 13 | "ask-sdk-dynamodb-persistence-adapter": "2.0.7", 14 | "ask-sdk-model": "1.5.0" 15 | } 16 | }, 17 | "ask-sdk-core": { 18 | "version": "2.0.7", 19 | "resolved": "https://registry.npmjs.org/ask-sdk-core/-/ask-sdk-core-2.0.7.tgz", 20 | "integrity": "sha512-L0YoF7ls0iUoo/WYDYj7uDjAFThYZSDjzF8YvLHIEZyzKVgrNNqxetRorUB+odDoctPWW7RV1xcep8F4p7c1jg==" 21 | }, 22 | "ask-sdk-dynamodb-persistence-adapter": { 23 | "version": "2.0.7", 24 | "resolved": "https://registry.npmjs.org/ask-sdk-dynamodb-persistence-adapter/-/ask-sdk-dynamodb-persistence-adapter-2.0.7.tgz", 25 | "integrity": "sha512-RHIOOAfsIwZy7hUtENPF12l1NldB8+rRZ3Jk4I9efpFFZSmXOM510Qho3Lt3OZCH9qTDRp1QWrQvn3ki7fdM8A==", 26 | "requires": { 27 | "aws-sdk": "2.311.0" 28 | } 29 | }, 30 | "ask-sdk-model": { 31 | "version": "1.5.0", 32 | "resolved": "https://registry.npmjs.org/ask-sdk-model/-/ask-sdk-model-1.5.0.tgz", 33 | "integrity": "sha512-CpHtK17hmwDvHeNLEV+5QoudMTDibji9WuAA57lT0Evpygc0nrCdJtb7llfEsiomE6LKg/faZkqg4QSwVeGtoQ==" 34 | }, 35 | "aws-sdk": { 36 | "version": "2.311.0", 37 | "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.311.0.tgz", 38 | "integrity": "sha512-OnIvCu3146CLCy9uEf9jj9PlIpKz5Notz6EDV0FQFMdE7QqKCBUFVgfS+lqQCiKOTMTDx+z+9MJmNLUMyGqIjA==", 39 | "requires": { 40 | "buffer": "4.9.1", 41 | "events": "1.1.1", 42 | "ieee754": "1.1.8", 43 | "jmespath": "0.15.0", 44 | "querystring": "0.2.0", 45 | "sax": "1.2.1", 46 | "url": "0.10.3", 47 | "uuid": "3.1.0", 48 | "xml2js": "0.4.19" 49 | } 50 | }, 51 | "axios": { 52 | "version": "0.18.0", 53 | "resolved": "http://registry.npmjs.org/axios/-/axios-0.18.0.tgz", 54 | "integrity": "sha1-MtU+SFHv3AoRmTts0AB4nXDAUQI=", 55 | "requires": { 56 | "follow-redirects": "1.5.7", 57 | "is-buffer": "1.1.6" 58 | } 59 | }, 60 | "base64-js": { 61 | "version": "1.3.0", 62 | "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.0.tgz", 63 | "integrity": "sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw==" 64 | }, 65 | "buffer": { 66 | "version": "4.9.1", 67 | "resolved": "http://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", 68 | "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", 69 | "requires": { 70 | "base64-js": "1.3.0", 71 | "ieee754": "1.1.8", 72 | "isarray": "1.0.0" 73 | } 74 | }, 75 | "debug": { 76 | "version": "3.1.0", 77 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", 78 | "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", 79 | "requires": { 80 | "ms": "2.0.0" 81 | } 82 | }, 83 | "events": { 84 | "version": "1.1.1", 85 | "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", 86 | "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=" 87 | }, 88 | "follow-redirects": { 89 | "version": "1.5.7", 90 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.7.tgz", 91 | "integrity": "sha512-NONJVIFiX7Z8k2WxfqBjtwqMifx7X42ORLFrOZ2LTKGj71G3C0kfdyTqGqr8fx5zSX6Foo/D95dgGWbPUiwnew==", 92 | "requires": { 93 | "debug": "3.1.0" 94 | } 95 | }, 96 | "ieee754": { 97 | "version": "1.1.8", 98 | "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.8.tgz", 99 | "integrity": "sha1-vjPUCsEO8ZJnAfbwii2G+/0a0+Q=" 100 | }, 101 | "is-buffer": { 102 | "version": "1.1.6", 103 | "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", 104 | "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" 105 | }, 106 | "isarray": { 107 | "version": "1.0.0", 108 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 109 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" 110 | }, 111 | "jmespath": { 112 | "version": "0.15.0", 113 | "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz", 114 | "integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc=" 115 | }, 116 | "moment": { 117 | "version": "2.22.2", 118 | "resolved": "https://registry.npmjs.org/moment/-/moment-2.22.2.tgz", 119 | "integrity": "sha1-PCV/mDn8DpP/UxSWMiOeuQeD/2Y=" 120 | }, 121 | "ms": { 122 | "version": "2.0.0", 123 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 124 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 125 | }, 126 | "punycode": { 127 | "version": "1.3.2", 128 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", 129 | "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" 130 | }, 131 | "querystring": { 132 | "version": "0.2.0", 133 | "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", 134 | "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" 135 | }, 136 | "sax": { 137 | "version": "1.2.1", 138 | "resolved": "http://registry.npmjs.org/sax/-/sax-1.2.1.tgz", 139 | "integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o=" 140 | }, 141 | "url": { 142 | "version": "0.10.3", 143 | "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", 144 | "integrity": "sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=", 145 | "requires": { 146 | "punycode": "1.3.2", 147 | "querystring": "0.2.0" 148 | } 149 | }, 150 | "uuid": { 151 | "version": "3.1.0", 152 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz", 153 | "integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g==" 154 | }, 155 | "xml2js": { 156 | "version": "0.4.19", 157 | "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", 158 | "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", 159 | "requires": { 160 | "sax": "1.2.1", 161 | "xmlbuilder": "9.0.7" 162 | } 163 | }, 164 | "xmlbuilder": { 165 | "version": "9.0.7", 166 | "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", 167 | "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=" 168 | } 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /Chapter04/weatherGods/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "weathergods", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "ask-sdk": "^2.0.7", 13 | "axios": "^0.18.0", 14 | "moment": "^2.22.2" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Chapter05/All-Lex-Responses.js: -------------------------------------------------------------------------------- 1 | const lexElicitSlot = ({ sessionAttributes = {}, message, intentName, slotToElicit, slots }) => { 2 | return { 3 | sessionAttributes, 4 | dialogAction: { 5 | type: 'ElicitSlot', 6 | intentName, 7 | slots, 8 | slotToElicit, 9 | message: { contentType: 'PlainText', content: message } 10 | }, 11 | }; 12 | } 13 | 14 | const lexElicitIntent = ({ message, sessionAttributes = {} }) => { 15 | return { 16 | sessionAttributes, 17 | dialogAction: { 18 | type: 'ElicitIntent', 19 | message: { contentType: 'PlainText', content: message } 20 | }, 21 | }; 22 | } 23 | 24 | const lexConfirmIntent = ({ sessionAttributes = {}, intentName, slots, message }) => { 25 | return { 26 | sessionAttributes, 27 | dialogAction: { 28 | type: 'ConfirmIntent', 29 | intentName, 30 | slots, 31 | message: { contentType: 'PlainText', content: message } 32 | }, 33 | }; 34 | } 35 | 36 | const lexClose = ({ sessionAttributes = {}, fulfillmentState = 'Fulfilled', message }) => { 37 | return { 38 | sessionAttributes, 39 | dialogAction: { 40 | type: 'Close', 41 | fulfillmentState, 42 | message: { contentType: 'PlainText', content: message } 43 | }, 44 | }; 45 | } 46 | 47 | const lexDelegate = ({ sessionAttributes = {}, slots }) => { 48 | return { 49 | sessionAttributes, 50 | dialogAction: { type: 'Delegate', slots, } 51 | }; 52 | } -------------------------------------------------------------------------------- /Chapter05/CL-other/archive.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Hands-On-Chatbot-Development-with-Alexa-Skills-and-Amazon-Lex/a90bd11fcdb4c0c42b7d30094413e36106826d1f/Chapter05/CL-other/archive.zip -------------------------------------------------------------------------------- /Chapter05/CL-other/index.js: -------------------------------------------------------------------------------- 1 | const AWS = require('aws-sdk'); 2 | const s3 = new AWS.S3(); 3 | 4 | exports.handler = async event => { 5 | 6 | console.log(event); 7 | let intentName = event.currentIntent.name; 8 | 9 | var params = { 10 | Bucket: 'cl-faq', 11 | Key: `faq-other.json` 12 | }; 13 | 14 | return new Promise((resolve, reject) => { 15 | s3.getObject(params, function(err, data) { 16 | if (err) { // an error occurred 17 | reject(handleS3Error(err)); 18 | } else { // successful response 19 | console.log(data); 20 | resolve(handleS3Data(data, intentName)); 21 | } 22 | }); 23 | }) 24 | }; 25 | 26 | const handleS3Error = err => { 27 | console.log('error of: ', err); 28 | let errResponse = `Unfortunately I don't know how to answer that. Is there anything else I can help you with?`; 29 | return lexElicitIntent({ message: errResponse }); 30 | } 31 | 32 | const handleS3Data = (data, intentName) => { 33 | let body = JSON.parse(data.Body); 34 | if (!body[intentName]) { 35 | return handleS3Error(`Intent name ${intentName} was not present in faq-other.json`); 36 | } 37 | return lexClose({ message: body[intentName] }); 38 | } 39 | 40 | const lexClose = ({ message, sessionAttributes = {}, fulfillmentState = "Fulfilled" }) => { 41 | return { 42 | sessionAttributes, 43 | dialogAction: { 44 | type: 'Close', 45 | fulfillmentState, 46 | message: { contentType: 'PlainText', content: message } 47 | } 48 | } 49 | } 50 | 51 | const lexElicitIntent = ({ message, sessionAttributes = {} }) => { 52 | return { 53 | sessionAttributes, 54 | dialogAction: { 55 | type: 'ElicitIntent', 56 | message: { contentType: 'PlainText', content: message } 57 | }, 58 | }; 59 | } -------------------------------------------------------------------------------- /Chapter05/CL-other/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cl-other", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "aws-sdk": { 8 | "version": "2.249.1", 9 | "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.249.1.tgz", 10 | "integrity": "sha1-KenvNa+xTODpH5kFsz7TVEF7lls=", 11 | "requires": { 12 | "buffer": "4.9.1", 13 | "events": "1.1.1", 14 | "ieee754": "1.1.8", 15 | "jmespath": "0.15.0", 16 | "querystring": "0.2.0", 17 | "sax": "1.2.1", 18 | "url": "0.10.3", 19 | "uuid": "3.1.0", 20 | "xml2js": "0.4.17" 21 | } 22 | }, 23 | "base64-js": { 24 | "version": "1.3.0", 25 | "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.0.tgz", 26 | "integrity": "sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw==" 27 | }, 28 | "buffer": { 29 | "version": "4.9.1", 30 | "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", 31 | "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", 32 | "requires": { 33 | "base64-js": "1.3.0", 34 | "ieee754": "1.1.8", 35 | "isarray": "1.0.0" 36 | } 37 | }, 38 | "events": { 39 | "version": "1.1.1", 40 | "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", 41 | "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=" 42 | }, 43 | "ieee754": { 44 | "version": "1.1.8", 45 | "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.8.tgz", 46 | "integrity": "sha1-vjPUCsEO8ZJnAfbwii2G+/0a0+Q=" 47 | }, 48 | "isarray": { 49 | "version": "1.0.0", 50 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 51 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" 52 | }, 53 | "jmespath": { 54 | "version": "0.15.0", 55 | "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz", 56 | "integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc=" 57 | }, 58 | "lodash": { 59 | "version": "4.17.10", 60 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", 61 | "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==" 62 | }, 63 | "punycode": { 64 | "version": "1.3.2", 65 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", 66 | "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" 67 | }, 68 | "querystring": { 69 | "version": "0.2.0", 70 | "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", 71 | "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" 72 | }, 73 | "sax": { 74 | "version": "1.2.1", 75 | "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", 76 | "integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o=" 77 | }, 78 | "url": { 79 | "version": "0.10.3", 80 | "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", 81 | "integrity": "sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=", 82 | "requires": { 83 | "punycode": "1.3.2", 84 | "querystring": "0.2.0" 85 | } 86 | }, 87 | "uuid": { 88 | "version": "3.1.0", 89 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz", 90 | "integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g==" 91 | }, 92 | "xml2js": { 93 | "version": "0.4.17", 94 | "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.17.tgz", 95 | "integrity": "sha1-F76T6q4/O3eTWceVtBlwWogX6Gg=", 96 | "requires": { 97 | "sax": "1.2.1", 98 | "xmlbuilder": "4.2.1" 99 | } 100 | }, 101 | "xmlbuilder": { 102 | "version": "4.2.1", 103 | "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-4.2.1.tgz", 104 | "integrity": "sha1-qlijBBoGb5DqoWwvU4n/GfP0YaU=", 105 | "requires": { 106 | "lodash": "4.17.10" 107 | } 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /Chapter05/CL-other/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cl-other", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "aws-sdk": "^2.249.1" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Chapter05/CL-setup/archive.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Hands-On-Chatbot-Development-with-Alexa-Skills-and-Amazon-Lex/a90bd11fcdb4c0c42b7d30094413e36106826d1f/Chapter05/CL-setup/archive.zip -------------------------------------------------------------------------------- /Chapter05/CL-setup/index.js: -------------------------------------------------------------------------------- 1 | const AWS = require('aws-sdk'); 2 | const s3 = new AWS.S3(); 3 | 4 | exports.handler = async event => { 5 | 6 | console.log(event); 7 | let intentName = event.currentIntent.name; 8 | 9 | var params = { 10 | Bucket: 'cl-faq', 11 | Key: `faq-setup.json` 12 | }; 13 | 14 | return new Promise((resolve, reject) => { 15 | s3.getObject(params, function(err, data) { 16 | if (err) { // an error occurred 17 | reject(handleS3Error(err)); 18 | } else { // successful response 19 | console.log(data); 20 | resolve(handleS3Data(data, intentName)); 21 | } 22 | }); 23 | }) 24 | }; 25 | 26 | const handleS3Error = err => { 27 | console.log('error of: ', err); 28 | let errResponse = `Unfortunately I don't know how to answer that. Is there anything else I can help you with?`; 29 | return lexElicitIntent({ message: errResponse }); 30 | } 31 | 32 | const handleS3Data = (data, intentName) => { 33 | let body = JSON.parse(data.Body); 34 | if (!body[intentName]) { 35 | return handleS3Error(`Intent name ${intentName} was not present in faq-setup.json`); 36 | } 37 | return lexClose({ message: body[intentName] }); 38 | } 39 | 40 | const lexClose = ({ message, sessionAttributes = {}, fulfillmentState = "Fulfilled" }) => { 41 | return { 42 | sessionAttributes, 43 | dialogAction: { 44 | type: 'Close', 45 | fulfillmentState, 46 | message: { contentType: 'PlainText', content: message } 47 | } 48 | } 49 | } 50 | 51 | const lexElicitIntent = ({ message, sessionAttributes = {} }) => { 52 | return { 53 | sessionAttributes, 54 | dialogAction: { 55 | type: 'ElicitIntent', 56 | message: { contentType: 'PlainText', content: message } 57 | }, 58 | }; 59 | } -------------------------------------------------------------------------------- /Chapter05/CL-setup/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cl-setup", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "aws-sdk": { 8 | "version": "2.249.1", 9 | "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.249.1.tgz", 10 | "integrity": "sha1-KenvNa+xTODpH5kFsz7TVEF7lls=", 11 | "dev": true, 12 | "requires": { 13 | "buffer": "4.9.1", 14 | "events": "1.1.1", 15 | "ieee754": "1.1.8", 16 | "jmespath": "0.15.0", 17 | "querystring": "0.2.0", 18 | "sax": "1.2.1", 19 | "url": "0.10.3", 20 | "uuid": "3.1.0", 21 | "xml2js": "0.4.17" 22 | } 23 | }, 24 | "base64-js": { 25 | "version": "1.3.0", 26 | "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.0.tgz", 27 | "integrity": "sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw==", 28 | "dev": true 29 | }, 30 | "buffer": { 31 | "version": "4.9.1", 32 | "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", 33 | "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", 34 | "dev": true, 35 | "requires": { 36 | "base64-js": "1.3.0", 37 | "ieee754": "1.1.8", 38 | "isarray": "1.0.0" 39 | } 40 | }, 41 | "events": { 42 | "version": "1.1.1", 43 | "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", 44 | "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=", 45 | "dev": true 46 | }, 47 | "ieee754": { 48 | "version": "1.1.8", 49 | "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.8.tgz", 50 | "integrity": "sha1-vjPUCsEO8ZJnAfbwii2G+/0a0+Q=", 51 | "dev": true 52 | }, 53 | "isarray": { 54 | "version": "1.0.0", 55 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 56 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", 57 | "dev": true 58 | }, 59 | "jmespath": { 60 | "version": "0.15.0", 61 | "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz", 62 | "integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc=", 63 | "dev": true 64 | }, 65 | "lodash": { 66 | "version": "4.17.10", 67 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", 68 | "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==", 69 | "dev": true 70 | }, 71 | "punycode": { 72 | "version": "1.3.2", 73 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", 74 | "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", 75 | "dev": true 76 | }, 77 | "querystring": { 78 | "version": "0.2.0", 79 | "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", 80 | "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", 81 | "dev": true 82 | }, 83 | "sax": { 84 | "version": "1.2.1", 85 | "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", 86 | "integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o=", 87 | "dev": true 88 | }, 89 | "url": { 90 | "version": "0.10.3", 91 | "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", 92 | "integrity": "sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=", 93 | "dev": true, 94 | "requires": { 95 | "punycode": "1.3.2", 96 | "querystring": "0.2.0" 97 | } 98 | }, 99 | "uuid": { 100 | "version": "3.1.0", 101 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz", 102 | "integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g==", 103 | "dev": true 104 | }, 105 | "xml2js": { 106 | "version": "0.4.17", 107 | "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.17.tgz", 108 | "integrity": "sha1-F76T6q4/O3eTWceVtBlwWogX6Gg=", 109 | "dev": true, 110 | "requires": { 111 | "sax": "1.2.1", 112 | "xmlbuilder": "4.2.1" 113 | } 114 | }, 115 | "xmlbuilder": { 116 | "version": "4.2.1", 117 | "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-4.2.1.tgz", 118 | "integrity": "sha1-qlijBBoGb5DqoWwvU4n/GfP0YaU=", 119 | "dev": true, 120 | "requires": { 121 | "lodash": "4.17.10" 122 | } 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /Chapter05/CL-setup/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cl-setup", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "devDependencies": { 12 | "aws-sdk": "^2.249.1" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Chapter05/CL-users/archive.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Hands-On-Chatbot-Development-with-Alexa-Skills-and-Amazon-Lex/a90bd11fcdb4c0c42b7d30094413e36106826d1f/Chapter05/CL-users/archive.zip -------------------------------------------------------------------------------- /Chapter05/CL-users/index.js: -------------------------------------------------------------------------------- 1 | const AWS = require('aws-sdk'); 2 | const s3 = new AWS.S3(); 3 | 4 | exports.handler = async event => { 5 | 6 | console.log(event); 7 | let intentName = event.currentIntent.name; 8 | 9 | var params = { 10 | Bucket: 'cl-faq', 11 | Key: `faq-users.json` 12 | }; 13 | 14 | return new Promise((resolve, reject) => { 15 | s3.getObject(params, function(err, data) { 16 | if (err) { // an error occurred 17 | reject(handleS3Error(err)); 18 | } else { // successful response 19 | console.log(data); 20 | resolve(handleS3Data(data, intentName)); 21 | } 22 | }); 23 | }) 24 | }; 25 | 26 | const handleS3Error = err => { 27 | console.log('error of: ', err); 28 | let errResponse = `Unfortunately I don't know how to answer that. Is there anything else I can help you with?`; 29 | return lexElicitIntent({ message: errResponse }); 30 | } 31 | 32 | const handleS3Data = (data, intentName) => { 33 | let body = JSON.parse(data.Body); 34 | if (!body[intentName]) { 35 | return handleS3Error(`Intent name ${intentName} was not present in faq-users.json`); 36 | } 37 | return lexClose({ message: body[intentName] }); 38 | } 39 | 40 | const lexClose = ({ message, sessionAttributes = {}, fulfillmentState = "Fulfilled" }) => { 41 | return { 42 | sessionAttributes, 43 | dialogAction: { 44 | type: 'Close', 45 | fulfillmentState, 46 | message: { contentType: 'PlainText', content: message } 47 | } 48 | } 49 | } 50 | 51 | const lexElicitIntent = ({ message, sessionAttributes = {} }) => { 52 | return { 53 | sessionAttributes, 54 | dialogAction: { 55 | type: 'ElicitIntent', 56 | message: { contentType: 'PlainText', content: message } 57 | }, 58 | }; 59 | } -------------------------------------------------------------------------------- /Chapter05/CL-users/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cl-users", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "aws-sdk": { 8 | "version": "2.249.1", 9 | "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.249.1.tgz", 10 | "integrity": "sha1-KenvNa+xTODpH5kFsz7TVEF7lls=", 11 | "requires": { 12 | "buffer": "4.9.1", 13 | "events": "1.1.1", 14 | "ieee754": "1.1.8", 15 | "jmespath": "0.15.0", 16 | "querystring": "0.2.0", 17 | "sax": "1.2.1", 18 | "url": "0.10.3", 19 | "uuid": "3.1.0", 20 | "xml2js": "0.4.17" 21 | } 22 | }, 23 | "base64-js": { 24 | "version": "1.3.0", 25 | "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.0.tgz", 26 | "integrity": "sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw==" 27 | }, 28 | "buffer": { 29 | "version": "4.9.1", 30 | "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", 31 | "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", 32 | "requires": { 33 | "base64-js": "1.3.0", 34 | "ieee754": "1.1.8", 35 | "isarray": "1.0.0" 36 | } 37 | }, 38 | "events": { 39 | "version": "1.1.1", 40 | "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", 41 | "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=" 42 | }, 43 | "ieee754": { 44 | "version": "1.1.8", 45 | "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.8.tgz", 46 | "integrity": "sha1-vjPUCsEO8ZJnAfbwii2G+/0a0+Q=" 47 | }, 48 | "isarray": { 49 | "version": "1.0.0", 50 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 51 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" 52 | }, 53 | "jmespath": { 54 | "version": "0.15.0", 55 | "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz", 56 | "integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc=" 57 | }, 58 | "lodash": { 59 | "version": "4.17.10", 60 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", 61 | "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==" 62 | }, 63 | "punycode": { 64 | "version": "1.3.2", 65 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", 66 | "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" 67 | }, 68 | "querystring": { 69 | "version": "0.2.0", 70 | "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", 71 | "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" 72 | }, 73 | "sax": { 74 | "version": "1.2.1", 75 | "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", 76 | "integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o=" 77 | }, 78 | "url": { 79 | "version": "0.10.3", 80 | "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", 81 | "integrity": "sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=", 82 | "requires": { 83 | "punycode": "1.3.2", 84 | "querystring": "0.2.0" 85 | } 86 | }, 87 | "uuid": { 88 | "version": "3.1.0", 89 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz", 90 | "integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g==" 91 | }, 92 | "xml2js": { 93 | "version": "0.4.17", 94 | "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.17.tgz", 95 | "integrity": "sha1-F76T6q4/O3eTWceVtBlwWogX6Gg=", 96 | "requires": { 97 | "sax": "1.2.1", 98 | "xmlbuilder": "4.2.1" 99 | } 100 | }, 101 | "xmlbuilder": { 102 | "version": "4.2.1", 103 | "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-4.2.1.tgz", 104 | "integrity": "sha1-qlijBBoGb5DqoWwvU4n/GfP0YaU=", 105 | "requires": { 106 | "lodash": "4.17.10" 107 | } 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /Chapter05/CL-users/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cl-users", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "aws-sdk": "^2.249.1" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Chapter05/data/faq-other.json: -------------------------------------------------------------------------------- 1 | { 2 | "userWorldwide": "CircleLoop will work in most countries around the world, providing you have a data or WiFi connection available. However, there are a small number of countries which block internet-based calling for either security or revenue-protection reasons. Internet-based calling is currently blocked in Azerbaijan, Belize, China, Iran, Jordan, Kuwait, Libya, North Korea, Oman, Pakistan, Qatar, Saudi Arabia, South Korea, Syria and UAE. You can still call these countries using CircleLoop, it’s just outbound calls that are blocked.", 3 | "headsets": "Yes. CircleLoop supports USB/Bluetooth headsets. We can recommend phone headsets that have been fully tested with CircleLoop. Please note that we don't support traditional IP deskphones.", 4 | "replaceExpenses": "Yes. We can save you some money. Your colleagues can simply add their business phone numbers to their own smartphones and computer systems to make and receive calls. There’s no longer any need for extra business phones or extra sim cards.", 5 | "cancelExistingContract": "That's up to you. You don't need an existing landline or mobile contract to use CircleLoop. Of course, if your internet connection is provided through your landline or mobile, you’ll still need that.", 6 | "dataUsage": "As a general guide, CircleLoop will use approx 1mb of data per minute. This can be variable, depending on which additional features you are using. CircleLoop’s data consumption is at most equivalent to most other IP or cloud-based systems.", 7 | "noSignal": "If you don't have a data connection, your call can be automatically diverted to a mobile number. You can set this up in the mobile apps." 8 | 9 | } -------------------------------------------------------------------------------- /Chapter05/data/faq-setup.json: -------------------------------------------------------------------------------- 1 | { 2 | "howItWorks": "CircleLoop is a cloud-based business phone system, which allows you to make and receive calls on any device, anywhere. It works using our powerful apps (Mac, Windows, iPhone, Android) which allow you to make and receive calls, shows all your call history and let's you manage your settings.", 3 | "technicalKnowledge": "No. We’ve made it really easy with our simple apps. As long as you can operate the basic functions on a mobile phone or a computer system, you’ll find CircleLoop a breeze to setup use. Simply download our apps, and you’re ready to go.", 4 | "waitingTime": "Once you've signed up and installed one of our apps, your account is ready to use instantly!", 5 | "mobileData": "We recommend having a 4G connection for high quality calls on mobile data. In the event that you don’t have a connection available, we’ll automatically push calls to your mobile number.", 6 | "makeAndReceiveCalls": "It couldn’t be easier. Install one of the CircleLoop apps (Mac, Windows, iPhone or Android), log in to your account and you're ready to go.", 7 | "unlimited": "Unlimited calls to UK 01, 02, 03 numbers and major network 07 numbers are included, along with calls to USA, Canada and Ireland (with a small number of exceptions) within fair usage limits.", 8 | "receivingCost": "No. Receiving calls is free of charge", 9 | "internationalCountries": "You can make calls to over 170 countries. Please refer to our pricing page for more details.", 10 | "costOutsidePlan": "Please refer to our pricing page for details of all call costs.", 11 | "contract": "No, there’s no commitment with CircleLoop. You just need to accept our terms and conditions.", 12 | "sevenDayTrial": "Full user privileges, including the ability to add users and numbers, plus 30 minutes of free UK landline & mobile calls. Please note that you cannot make international or premium rate calls during your trial period." 13 | } -------------------------------------------------------------------------------- /Chapter05/data/faq-users.json: -------------------------------------------------------------------------------- 1 | { 2 | "extraUsers": "CircleLoop is self-service, so just add users using the apps in just a few clicks.", 3 | "extraNumbers": "CircleLoop is self-service, so just add/remove numbers using the apps in a couple of clicks.", 4 | "internationalPhone": "Yes. For a small additional monthly subscription, we offer a range of telephone numbers from major international cities such as New York, Toronto, Amsterdam, Sydney and Dublin.", 5 | "transferNumber": "Yes, we’re happy to transfer an existing number to CircleLoop. We can port most landline numbers for £20+VAT. We also port in from all UK major networks free of charge. Please note that we can't port international numbers.", 6 | "portingTime": "It varies. Mobile numbers are really straightforward and take a couple of days. Other numbers can take anything from 7 to 20 days depending on the provider we are transferring from. The process is quite old-fashioned and very fussy. Unfortunately, it’s not something that we’re in control of, but we’ll always do our best to speed up the process for you.", 7 | "leaving": "Once you're a paying user, any numbers on your account are available for you to take elsewhere if your new provider supports this." 8 | } -------------------------------------------------------------------------------- /Chapter06/addToCart/DB.js: -------------------------------------------------------------------------------- 1 | const AWS = require('aws-sdk'); 2 | 3 | let documentClient = new AWS.DynamoDB.DocumentClient({ 4 | 'region': 'eu-west-1' 5 | }); 6 | 7 | module.exports = class DB { 8 | write(ID, data, table) { 9 | return new Promise((resolve, reject) => { 10 | if (!ID) throw 'An ID is needed'; 11 | if (typeof ID !== 'string') throw `the id must be a string and not ${ID}` 12 | if (!data) throw "data is needed"; 13 | if (!table) throw 'table name is needed'; 14 | if (typeof table !== 'string') throw `the table name must be a string and not ${table}` 15 | 16 | let params = { 17 | TableName: table, 18 | Item: { ...data, ID: ID } 19 | }; 20 | 21 | documentClient.put(params, function(err, result) { 22 | if (err) { 23 | console.log("Err in writeForCall writing messages to dynamo:", err); 24 | console.log(params); 25 | return reject(err); 26 | } 27 | console.log('wrote data to table ', table) 28 | return resolve({ ...result.Attributes, ...params.Item }); 29 | }); 30 | }) 31 | }; 32 | 33 | get(key, value, table) { 34 | if (!table) throw 'table needed'; 35 | if (typeof key !== 'string') throw `key was not string and was ${JSON.stringify(key)} on table ${table}`; 36 | if (typeof value !== 'string') throw `value was not string and was ${JSON.stringify(value)} on table ${table}`; 37 | if (!table) 'table needs to be users, sessions, or routes.' 38 | return new Promise((resolve, reject) => { 39 | let params = { 40 | TableName: table, 41 | Key: { 42 | [key]: value 43 | } 44 | }; 45 | documentClient.get(params, function(err, data) { 46 | if (err) { 47 | console.log(`There was an error fetching the data for ${key} ${value} on table ${table}`, err); 48 | return reject(err); 49 | } 50 | //TODO check only one Item. 51 | return resolve(data.Item); 52 | }); 53 | }); 54 | } 55 | 56 | getDifferent(key, value, table) { 57 | if (!table) throw 'table needed'; 58 | if (typeof key !== 'string') throw `key was not string and was ${JSON.stringify(key)} on table ${table}`; 59 | if (typeof value !== 'string') throw `value was not string and was ${JSON.stringify(value)} on table ${table}`; 60 | if (!table) 'table needs to be users, sessions, or routes.' 61 | return new Promise((resolve, reject) => { 62 | var params = { 63 | TableName: table, 64 | IndexName: `${key}-index`, 65 | KeyConditionExpression: `${key} = :value`, 66 | ExpressionAttributeValues: { 67 | ':value': value 68 | } 69 | }; 70 | 71 | documentClient.query(params, function(err, data) { 72 | if (err) { 73 | console.error("Unable to read item. Error JSON:", JSON.stringify(err)); 74 | reject(err); 75 | } else { 76 | console.log("GetItem succeeded:", JSON.stringify(data.Items)); 77 | resolve(data.Items); 78 | } 79 | }); 80 | }) 81 | } 82 | 83 | async update(ID, table, key, value) { 84 | let data = await this.get(ID, table); 85 | return this.write(ID, { ...data, [key]: value }, table); 86 | } 87 | 88 | delete(ID, table) { 89 | if (!table) throw 'table needed'; 90 | if (typeof ID !== 'string') throw `ID was not string and was ${JSON.stringify(ID)} on table ${table}`; 91 | console.log("dynamo deleting record ID", ID, 'from table ', table); 92 | let params = { 93 | TableName: table, 94 | Key: { 95 | ID 96 | } 97 | }; 98 | 99 | return new Promise((resolve, reject) => { 100 | documentClient.delete(params, function(err, data) { 101 | if (err) { 102 | reject(err); 103 | } else { 104 | resolve(data); 105 | } 106 | }); 107 | }); 108 | 109 | } 110 | 111 | } -------------------------------------------------------------------------------- /Chapter06/addToCart/LexResponses.js: -------------------------------------------------------------------------------- 1 | module.exports = class Lex { 2 | 3 | elicitSlot({ sessionAttributes = {}, message, intentName, slotToElicit, slots }) { 4 | return { 5 | sessionAttributes, 6 | dialogAction: { 7 | type: 'ElicitSlot', 8 | intentName, 9 | slots, 10 | slotToElicit, 11 | message: { contentType: 'PlainText', content: message } 12 | }, 13 | }; 14 | } 15 | 16 | close({ message, sessionAttributes = {}, fulfillmentState = "Fulfilled" }) { 17 | return { 18 | sessionAttributes, 19 | dialogAction: { 20 | type: 'Close', 21 | fulfillmentState, 22 | message: { contentType: 'PlainText', content: message } 23 | } 24 | } 25 | } 26 | 27 | elicitIntent({ message, sessionAttributes = {} }) { 28 | return { 29 | sessionAttributes, 30 | dialogAction: { 31 | type: 'ElicitIntent', 32 | message: { contentType: 'PlainText', content: message } 33 | }, 34 | }; 35 | } 36 | 37 | confirmIntent({ sessionAttributes = {}, intentName, slots, message }) { 38 | return { 39 | sessionAttributes, 40 | dialogAction: { 41 | type: 'ConfirmIntent', 42 | intentName, 43 | slots, 44 | message: { contentType: 'PlainText', content: message } 45 | }, 46 | }; 47 | } 48 | 49 | delegate({ sessionAttributes = {}, slots }) { 50 | return { 51 | sessionAttributes, 52 | dialogAction: { 53 | type: 'Delegate', 54 | slots, 55 | }, 56 | }; 57 | } 58 | } -------------------------------------------------------------------------------- /Chapter06/addToCart/archive.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Hands-On-Chatbot-Development-with-Alexa-Skills-and-Amazon-Lex/a90bd11fcdb4c0c42b7d30094413e36106826d1f/Chapter06/addToCart/archive.zip -------------------------------------------------------------------------------- /Chapter06/addToCart/index.js: -------------------------------------------------------------------------------- 1 | const lex = require('./LexResponses'); 2 | const Lex = new lex(); 3 | const db = require('./DB'); 4 | const DB = new db(); 5 | const uuidv4 = require('uuid/v4'); 6 | 7 | exports.handler = async (event) => { 8 | if (event.currentIntent && event.currentIntent.confirmationStatus === "Denied") { 9 | let message = `Would you like to find another product?`; 10 | let intentName = 'productFind'; 11 | let slots = { type: null, size: null, colour: null, length: null, itemNumber: null }; 12 | return Lex.confirmIntent({ intentName, slots, message }) 13 | } 14 | return handleAddToCart(event); 15 | } 16 | 17 | const handleAddToCart = async event => { 18 | let { slots } = event.currentIntent; 19 | let { itemNumber } = slots; 20 | 21 | if (!itemNumber) { 22 | let message = `You need to select a product before adding it to a cart. Would you like to find another product?`; 23 | let intentName = 'productFind'; 24 | slots = { type: null, size: null, colour: null, length: null, itemNumber: null }; 25 | return Lex.confirmIntent({ intentName, slots, message }); 26 | } 27 | 28 | 29 | let [err, cartUser] = await to(DB.get("ID", event.userId, 'shopping-cart')); 30 | if (!cartUser) { 31 | cartUser = { ID: event.userId, Items: [], name: uuidv4(), TTL: 0 }; 32 | } 33 | let updatedCart = { ...cartUser, ID: Math.random().toString(), userID: event.userId, Items: [...cartUser.Items, itemNumber], TTL: Date.now() + 7 * 24 * 60 * 60 * 1000 }; 34 | let [writeErr, res] = await to(DB.write(event.userId, updatedCart, 'shopping-cart')); 35 | if (writeErr) { 36 | let message = `Unfortunately we've had an error on our system and we can't add this to your cart.`; 37 | return Lex.close({ message }); 38 | } 39 | let message = `Would you like to checkout, add another item to your cart or save your cart for later?`; 40 | return Lex.elicitIntent({ message }); 41 | 42 | } 43 | 44 | 45 | const to = prom => prom.then(data => [null, data]).catch(err => [err, null]); -------------------------------------------------------------------------------- /Chapter06/addToCart/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "lockfileVersion": 1 3 | } 4 | -------------------------------------------------------------------------------- /Chapter06/addToCart/tests: -------------------------------------------------------------------------------- 1 | { "currentIntent": { "confirmationStatus": "Denied" } } 2 | 3 | 4 | { 5 | "currentIntent": { 6 | "confirmationStatus": "Confirmed", 7 | "slots": { 8 | "itemNumber": null 9 | } 10 | } 11 | } 12 | 13 | 14 | { 15 | "currentIntent": { 16 | "slots": { 17 | "itemNumber": 1034 18 | } 19 | } 20 | } 21 | 22 | 23 | -------------------------------------------------------------------------------- /Chapter06/checkout/DB.js: -------------------------------------------------------------------------------- 1 | const AWS = require('aws-sdk'); 2 | 3 | let documentClient = new AWS.DynamoDB.DocumentClient({ 4 | 'region': 'eu-west-1' 5 | }); 6 | 7 | module.exports = class DB { 8 | write(ID, data, table) { 9 | return new Promise((resolve, reject) => { 10 | if (!ID) throw 'An ID is needed'; 11 | if (typeof ID !== 'string') throw `the id must be a string and not ${ID}` 12 | if (!data) throw "data is needed"; 13 | if (!table) throw 'table name is needed'; 14 | if (typeof table !== 'string') throw `the table name must be a string and not ${table}` 15 | 16 | let params = { 17 | TableName: table, 18 | Item: { ...data, ID: ID } 19 | }; 20 | 21 | documentClient.put(params, function(err, result) { 22 | if (err) { 23 | console.log("Err in writeForCall writing messages to dynamo:", err); 24 | console.log(params); 25 | return reject(err); 26 | } 27 | console.log('wrote data to table ', table) 28 | return resolve({ ...result.Attributes, ...params.Item }); 29 | }); 30 | }) 31 | }; 32 | 33 | get(key, value, table) { 34 | if (!table) throw 'table needed'; 35 | if (typeof key !== 'string') throw `key was not string and was ${JSON.stringify(key)} on table ${table}`; 36 | if (typeof value !== 'string') throw `value was not string and was ${JSON.stringify(value)} on table ${table}`; 37 | if (!table) 'table needs to be users, sessions, or routes.' 38 | return new Promise((resolve, reject) => { 39 | let params = { 40 | TableName: table, 41 | Key: { 42 | [key]: value 43 | } 44 | }; 45 | documentClient.get(params, function(err, data) { 46 | if (err) { 47 | console.log(`There was an error fetching the data for ${key} ${value} on table ${table}`, err); 48 | return reject(err); 49 | } 50 | //TODO check only one Item. 51 | return resolve(data.Item); 52 | }); 53 | }); 54 | } 55 | 56 | getDifferent(key, value, table) { 57 | if (!table) throw 'table needed'; 58 | if (typeof key !== 'string') throw `key was not string and was ${JSON.stringify(key)} on table ${table}`; 59 | if (typeof value !== 'string') throw `value was not string and was ${JSON.stringify(value)} on table ${table}`; 60 | if (!table) 'table needs to be users, sessions, or routes.' 61 | return new Promise((resolve, reject) => { 62 | var params = { 63 | TableName: table, 64 | IndexName: `${key}-index`, 65 | KeyConditionExpression: `${key} = :value`, 66 | ExpressionAttributeValues: { 67 | ':value': value 68 | } 69 | }; 70 | 71 | documentClient.query(params, function(err, data) { 72 | if (err) { 73 | console.error("Unable to read item. Error JSON:", JSON.stringify(err)); 74 | reject(err); 75 | } else { 76 | console.log("GetItem succeeded:", JSON.stringify(data.Items)); 77 | resolve(data.Items); 78 | } 79 | }); 80 | }) 81 | } 82 | 83 | async update(ID, table, key, value) { 84 | let data = await this.get(ID, table); 85 | return this.write(ID, { ...data, [key]: value }, table); 86 | } 87 | 88 | delete(ID, table) { 89 | if (!table) throw 'table needed'; 90 | if (typeof ID !== 'string') throw `ID was not string and was ${JSON.stringify(ID)} on table ${table}`; 91 | console.log("dynamo deleting record ID", ID, 'from table ', table); 92 | let params = { 93 | TableName: table, 94 | Key: { 95 | ID 96 | } 97 | }; 98 | 99 | return new Promise((resolve, reject) => { 100 | documentClient.delete(params, function(err, data) { 101 | if (err) { 102 | reject(err); 103 | } else { 104 | resolve(data); 105 | } 106 | }); 107 | }); 108 | 109 | } 110 | 111 | } -------------------------------------------------------------------------------- /Chapter06/checkout/LexResponses.js: -------------------------------------------------------------------------------- 1 | module.exports = class Lex { 2 | 3 | elicitSlot({ sessionAttributes = {}, message, intentName, slotToElicit, slots }) { 4 | return { 5 | sessionAttributes, 6 | dialogAction: { 7 | type: 'ElicitSlot', 8 | intentName, 9 | slots, 10 | slotToElicit, 11 | message: { contentType: 'PlainText', content: message } 12 | }, 13 | }; 14 | } 15 | 16 | close({ message, sessionAttributes = {}, fulfillmentState = "Fulfilled" }) { 17 | return { 18 | sessionAttributes, 19 | dialogAction: { 20 | type: 'Close', 21 | fulfillmentState, 22 | message: { contentType: 'PlainText', content: message } 23 | } 24 | } 25 | } 26 | 27 | elicitIntent({ message, sessionAttributes = {} }) { 28 | return { 29 | sessionAttributes, 30 | dialogAction: { 31 | type: 'ElicitIntent', 32 | message: { contentType: 'PlainText', content: message } 33 | }, 34 | }; 35 | } 36 | 37 | confirmIntent({ sessionAttributes = {}, intentName, slots, message }) { 38 | return { 39 | sessionAttributes, 40 | dialogAction: { 41 | type: 'ConfirmIntent', 42 | intentName, 43 | slots, 44 | message: { contentType: 'PlainText', content: message } 45 | }, 46 | }; 47 | } 48 | 49 | delegate({ sessionAttributes = {}, slots }) { 50 | return { 51 | sessionAttributes, 52 | dialogAction: { 53 | type: 'Delegate', 54 | slots, 55 | }, 56 | }; 57 | } 58 | } -------------------------------------------------------------------------------- /Chapter06/checkout/archive.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Hands-On-Chatbot-Development-with-Alexa-Skills-and-Amazon-Lex/a90bd11fcdb4c0c42b7d30094413e36106826d1f/Chapter06/checkout/archive.zip -------------------------------------------------------------------------------- /Chapter06/checkout/index.js: -------------------------------------------------------------------------------- 1 | const lex = require('./LexResponses'); 2 | const Lex = new lex(); 3 | const db = require('./DB'); 4 | const DB = new db(); 5 | const uuidv4 = require('uuid/v4'); 6 | 7 | exports.handler = async (event) => { 8 | if (event.currentIntent && event.currentIntent.confirmationStatus === "Denied") { 9 | let message = `Would you like to find another product?`; 10 | let intentName = 'productFind'; 11 | slots = { type: null, size: null, colour: null, length: null, itemNumber: null }; 12 | return Lex.confirmIntent({ intentName, slots, message }); 13 | } 14 | return handleCheckout(event); 15 | } 16 | 17 | const handleCheckout = async event => { 18 | let { slots } = event.currentIntent; 19 | let { deliveryAddress } = slots; 20 | 21 | if (!deliveryAddress) { 22 | let message = `What address would you like this order delivered to?`; 23 | let intentName = 'checkout'; 24 | slots = { deliveryAddress: null }; 25 | let slotToElicit = 'deliveryAddress' 26 | return Lex.elicitSlot({ message, intentName, slots, slotToElicit }); 27 | } 28 | 29 | let [cartErr, cart] = await to(DB.get("ID", event.userId, 'shopping-cart')); 30 | if (!cart) { 31 | console.log('no cart'); 32 | let message = `We couldn't find your cart. Is there anything else I can help you with`; 33 | return Lex.elicitIntent({ message }); 34 | } 35 | 36 | let order = { Items: cart.Items, address: deliveryAddress, date: Date.now() }; 37 | let ID = uuidv4(); 38 | try { 39 | await to(DB.write(ID, order, 'shopping-orders')); 40 | await to(DB.delete(event.userId, 'shopping-cart')); 41 | } catch (err) { 42 | console.log('error deleting the cart or writing the order.', cartErr); 43 | let message = `I'm sorry, there was a system error so your order hasn't been placed.`; 44 | return Lex.close({ message }); 45 | } 46 | let message = `Thank you. Your order has been placed and will be delivered in 3-5 working days`; 47 | return Lex.close({ message }); 48 | } 49 | 50 | const to = prom => prom.then(res => [null, res]).catch(e => [e, null]); -------------------------------------------------------------------------------- /Chapter06/checkout/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "requires": true, 3 | "lockfileVersion": 1, 4 | "dependencies": { 5 | "uuid": { 6 | "version": "3.3.2", 7 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", 8 | "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Chapter06/checkout/tests: -------------------------------------------------------------------------------- 1 | 2 | { "currentIntent": { "confirmationStatus": "Denied" } } 3 | 4 | 5 | { 6 | "currentIntent": { 7 | "confirmationStatus": "Confirmed", 8 | "slots": { "deliveryAddress": null} 9 | } 10 | } 11 | 12 | 13 | { 14 | "currentIntent": { 15 | "confirmationStatus": "None", 16 | "slots": { "deliveryAddress": "123 imaginary street, fake town, madeupsville"} 17 | }, 18 | "userId": "fakeUser" 19 | } 20 | 21 | 22 | { 23 | "currentIntent": { 24 | "confirmationStatus": "None", 25 | "slots": { "deliveryAddress": "123 imaginary street, fake town, madeupsville"} 26 | }, 27 | "userId": ## paste your ID here 28 | } -------------------------------------------------------------------------------- /Chapter06/data/stock.json: -------------------------------------------------------------------------------- 1 | { 2 | "stock": [ 3 | { "itemNumber": 1000, "type": "trousers", "size": "small", "colour": "black", "length": "long", "stock": 3 }, 4 | { "itemNumber": 1001, "type": "trousers", "size": "small", "colour": "black", "length": "standard", "stock": 5 }, 5 | { "itemNumber": 1002, "type": "trousers", "size": "small", "colour": "black", "length": "short", "stock": 0 }, 6 | { "itemNumber": 1003, "type": "trousers", "size": "medium", "colour": "black", "length": "long", "stock": 2 }, 7 | { "itemNumber": 1004, "type": "trousers", "size": "medium", "colour": "black", "length": "standard", "stock": 8 }, 8 | { "itemNumber": 1005, "type": "trousers", "size": "medium", "colour": "black", "length": "short", "stock": 4 }, 9 | { "itemNumber": 1006, "type": "trousers", "size": "large", "colour": "black", "length": "long", "stock": 4 }, 10 | { "itemNumber": 1007, "type": "trousers", "size": "large", "colour": "black", "length": "standard", "stock": 4 }, 11 | { "itemNumber": 1008, "type": "trousers", "size": "large", "colour": "black", "length": "short", "stock": 1 }, 12 | 13 | { "itemNumber": 1009, "type": "shirt", "size": "small", "colour": "black", "stock": 6 }, 14 | { "itemNumber": 1010, "type": "shirt", "size": "medium", "colour": "black", "stock": 0 }, 15 | { "itemNumber": 1011, "type": "shirt", "size": "large", "colour": "black", "stock": 3 }, 16 | 17 | { "itemNumber": 1012, "type": "jacket", "size": "small", "colour": "black", "stock": 4 }, 18 | { "itemNumber": 1013, "type": "jacket", "size": "medium", "colour": "black", "stock": 6 }, 19 | { "itemNumber": 1014, "type": "jacket", "size": "large", "colour": "black", "stock": 8 }, 20 | 21 | 22 | { "itemNumber": 1015, "type": "trousers", "size": "small", "colour": "white", "length": "long", "stock": 0 }, 23 | { "itemNumber": 1016, "type": "trousers", "size": "small", "colour": "white", "length": "standard", "stock": 1 }, 24 | { "itemNumber": 1017, "type": "trousers", "size": "small", "colour": "white", "length": "short", "stock": 2 }, 25 | { "itemNumber": 1018, "type": "trousers", "size": "medium", "colour": "white", "length": "long", "stock": 2 }, 26 | { "itemNumber": 1019, "type": "trousers", "size": "medium", "colour": "white", "length": "standard", "stock": 1 }, 27 | { "itemNumber": 1020, "type": "trousers", "size": "medium", "colour": "white", "length": "short", "stock": 0 }, 28 | { "itemNumber": 1021, "type": "trousers", "size": "large", "colour": "white", "length": "long", "stock": 0 }, 29 | { "itemNumber": 1022, "type": "trousers", "size": "large", "colour": "white", "length": "standard", "stock": 3 }, 30 | { "itemNumber": 1023, "type": "trousers", "size": "large", "colour": "white", "length": "short", "stock": 1 }, 31 | 32 | { "itemNumber": 1024, "type": "shirt", "size": "small", "colour": "white", "stock": 14 }, 33 | { "itemNumber": 1025, "type": "shirt", "size": "medium", "colour": "white", "stock": 20 }, 34 | { "itemNumber": 1026, "type": "shirt", "size": "large", "colour": "white", "stock": 13 }, 35 | 36 | { "itemNumber": 1027, "type": "jacket", "size": "small", "colour": "white", "stock": 0 }, 37 | { "itemNumber": 1028, "type": "jacket", "size": "medium", "colour": "white", "stock": 2 }, 38 | { "itemNumber": 1029, "type": "jacket", "size": "large", "colour": "white", "stock": 2 }, 39 | 40 | 41 | { "itemNumber": 1030, "type": "trousers", "size": "small", "colour": "blue", "length": "long", "stock": 4 }, 42 | { "itemNumber": 1031, "type": "trousers", "size": "small", "colour": "blue", "length": "standard", "stock": 4 }, 43 | { "itemNumber": 1032, "type": "trousers", "size": "small", "colour": "blue", "length": "short", "stock": 3 }, 44 | { "itemNumber": 1033, "type": "trousers", "size": "medium", "colour": "blue", "length": "long", "stock": 3 }, 45 | { "itemNumber": 1034, "type": "trousers", "size": "medium", "colour": "blue", "length": "standard", "stock": 7 }, 46 | { "itemNumber": 1035, "type": "trousers", "size": "medium", "colour": "blue", "length": "short", "stock": 5 }, 47 | { "itemNumber": 1036, "type": "trousers", "size": "large", "colour": "blue", "length": "long", "stock": 3 }, 48 | { "itemNumber": 1037, "type": "trousers", "size": "large", "colour": "blue", "length": "standard", "stock": 1 }, 49 | { "itemNumber": 1038, "type": "trousers", "size": "large", "colour": "blue", "length": "short", "stock": 2 }, 50 | 51 | { "itemNumber": 1039, "type": "shirt", "size": "small", "colour": "blue", "stock": 7 }, 52 | { "itemNumber": 1040, "type": "shirt", "size": "medium", "colour": "blue", "stock": 8 }, 53 | { "itemNumber": 1041, "type": "shirt", "size": "large", "colour": "blue", "stock": 4 }, 54 | 55 | { "itemNumber": 1042, "type": "jacket", "size": "small", "colour": "blue", "stock": 1 }, 56 | { "itemNumber": 1043, "type": "jacket", "size": "medium", "colour": "blue", "stock": 5 }, 57 | { "itemNumber": 1044, "type": "jacket", "size": "large", "colour": "blue", "stock": 8 }, 58 | 59 | 60 | { "itemNumber": 1045, "type": "trousers", "size": "small", "colour": "red", "length": "long", "stock": 0 }, 61 | { "itemNumber": 1046, "type": "trousers", "size": "small", "colour": "red", "length": "standard", "stock": 1 }, 62 | { "itemNumber": 1047, "type": "trousers", "size": "small", "colour": "red", "length": "short", "stock": 0 }, 63 | { "itemNumber": 1048, "type": "trousers", "size": "medium", "colour": "red", "length": "long", "stock": 1 }, 64 | { "itemNumber": 1049, "type": "trousers", "size": "medium", "colour": "red", "length": "standard", "stock": 2 }, 65 | { "itemNumber": 1050, "type": "trousers", "size": "medium", "colour": "red", "length": "short", "stock": 0 }, 66 | { "itemNumber": 1051, "type": "trousers", "size": "large", "colour": "red", "length": "long", "stock": 1 }, 67 | { "itemNumber": 1052, "type": "trousers", "size": "large", "colour": "red", "length": "standard", "stock": 0 }, 68 | { "itemNumber": 1053, "type": "trousers", "size": "large", "colour": "red", "length": "short", "stock": 2 }, 69 | 70 | { "itemNumber": 1054, "type": "shirt", "size": "small", "colour": "red", "stock": 5 }, 71 | { "itemNumber": 1055, "type": "shirt", "size": "medium", "colour": "red", "stock": 8 }, 72 | { "itemNumber": 1056, "type": "shirt", "size": "large", "colour": "red", "stock": 4 }, 73 | 74 | { "itemNumber": 1057, "type": "jacket", "size": "small", "colour": "red", "stock": 2 }, 75 | { "itemNumber": 1058, "type": "jacket", "size": "medium", "colour": "red", "stock": 4 }, 76 | { "itemNumber": 1059, "type": "jacket", "size": "large", "colour": "red", "stock": 5 }, 77 | 78 | 79 | { "itemNumber": 1060, "type": "trousers", "size": "small", "colour": "pink", "length": "long", "stock": 0 }, 80 | { "itemNumber": 1061, "type": "trousers", "size": "small", "colour": "pink", "length": "standard", "stock": 0 }, 81 | { "itemNumber": 1062, "type": "trousers", "size": "small", "colour": "pink", "length": "short", "stock": 0 }, 82 | { "itemNumber": 1063, "type": "trousers", "size": "medium", "colour": "pink", "length": "long", "stock": 0 }, 83 | { "itemNumber": 1064, "type": "trousers", "size": "medium", "colour": "pink", "length": "standard", "stock": 0 }, 84 | { "itemNumber": 1065, "type": "trousers", "size": "medium", "colour": "pink", "length": "short", "stock": 0 }, 85 | { "itemNumber": 1066, "type": "trousers", "size": "large", "colour": "pink", "length": "long", "stock": 0 }, 86 | { "itemNumber": 1067, "type": "trousers", "size": "large", "colour": "pink", "length": "standard", "stock": 0 }, 87 | { "itemNumber": 1068, "type": "trousers", "size": "large", "colour": "pink", "length": "short", "stock": 0 }, 88 | 89 | { "itemNumber": 1069, "type": "shirt", "size": "small", "colour": "pink", "stock": 4 }, 90 | { "itemNumber": 1070, "type": "shirt", "size": "medium", "colour": "pink", "stock": 6 }, 91 | { "itemNumber": 1071, "type": "shirt", "size": "large", "colour": "pink", "stock": 5 }, 92 | 93 | { "itemNumber": 1072, "type": "jacket", "size": "small", "colour": "pink", "stock": 1 }, 94 | { "itemNumber": 1073, "type": "jacket", "size": "medium", "colour": "pink", "stock": 3 }, 95 | { "itemNumber": 1074, "type": "jacket", "size": "large", "colour": "pink", "stock": 4 } 96 | ] 97 | } -------------------------------------------------------------------------------- /Chapter06/getSavedCart/DB.js: -------------------------------------------------------------------------------- 1 | const AWS = require('aws-sdk'); 2 | 3 | let documentClient = new AWS.DynamoDB.DocumentClient({ 4 | 'region': 'eu-west-1' 5 | }); 6 | 7 | module.exports = class DB { 8 | write(ID, data, table) { 9 | return new Promise((resolve, reject) => { 10 | if (!ID) throw 'An ID is needed'; 11 | if (typeof ID !== 'string') throw `the id must be a string and not ${ID}` 12 | if (!data) throw "data is needed"; 13 | if (!table) throw 'table name is needed'; 14 | if (typeof table !== 'string') throw `the table name must be a string and not ${table}` 15 | 16 | let params = { 17 | TableName: table, 18 | Item: { ...data, ID: ID } 19 | }; 20 | 21 | documentClient.put(params, function(err, result) { 22 | if (err) { 23 | console.log("Err in writeForCall writing messages to dynamo:", err); 24 | console.log(params); 25 | return reject(err); 26 | } 27 | console.log('wrote data to table ', table) 28 | return resolve({ ...result.Attributes, ...params.Item }); 29 | }); 30 | }) 31 | }; 32 | 33 | get(key, value, table) { 34 | if (!table) throw 'table needed'; 35 | if (typeof key !== 'string') throw `key was not string and was ${JSON.stringify(key)} on table ${table}`; 36 | if (typeof value !== 'string') throw `value was not string and was ${JSON.stringify(value)} on table ${table}`; 37 | if (!table) 'table needs to be users, sessions, or routes.' 38 | return new Promise((resolve, reject) => { 39 | let params = { 40 | TableName: table, 41 | Key: { 42 | [key]: value 43 | } 44 | }; 45 | documentClient.get(params, function(err, data) { 46 | if (err) { 47 | console.log(`There was an error fetching the data for ${key} ${value} on table ${table}`, err); 48 | return reject(err); 49 | } 50 | //TODO check only one Item. 51 | return resolve(data.Item); 52 | }); 53 | }); 54 | } 55 | 56 | getDifferent(key, value, table) { 57 | if (!table) throw 'table needed'; 58 | if (typeof key !== 'string') throw `key was not string and was ${JSON.stringify(key)} on table ${table}`; 59 | if (typeof value !== 'string') throw `value was not string and was ${JSON.stringify(value)} on table ${table}`; 60 | if (!table) 'table needs to be users, sessions, or routes.' 61 | return new Promise((resolve, reject) => { 62 | var params = { 63 | TableName: table, 64 | IndexName: `${key}-index`, 65 | KeyConditionExpression: `${key} = :value`, 66 | ExpressionAttributeValues: { 67 | ':value': value 68 | } 69 | }; 70 | 71 | documentClient.query(params, function(err, data) { 72 | if (err) { 73 | console.error("Unable to read item. Error JSON:", JSON.stringify(err)); 74 | reject(err); 75 | } else { 76 | console.log("GetItem succeeded:", JSON.stringify(data.Items)); 77 | resolve(data.Items); 78 | } 79 | }); 80 | }) 81 | } 82 | 83 | async update(ID, table, key, value) { 84 | let data = await this.get(ID, table); 85 | return this.write(ID, { ...data, [key]: value }, table); 86 | } 87 | 88 | delete(ID, table) { 89 | if (!table) throw 'table needed'; 90 | if (typeof ID !== 'string') throw `ID was not string and was ${JSON.stringify(ID)} on table ${table}`; 91 | console.log("dynamo deleting record ID", ID, 'from table ', table); 92 | let params = { 93 | TableName: table, 94 | Key: { 95 | ID 96 | } 97 | }; 98 | 99 | return new Promise((resolve, reject) => { 100 | documentClient.delete(params, function(err, data) { 101 | if (err) { 102 | reject(err); 103 | } else { 104 | resolve(data); 105 | } 106 | }); 107 | }); 108 | 109 | } 110 | 111 | } -------------------------------------------------------------------------------- /Chapter06/getSavedCart/LexResponses.js: -------------------------------------------------------------------------------- 1 | module.exports = class Lex { 2 | 3 | elicitSlot({ sessionAttributes = {}, message, intentName, slotToElicit, slots }) { 4 | return { 5 | sessionAttributes, 6 | dialogAction: { 7 | type: 'ElicitSlot', 8 | intentName, 9 | slots, 10 | slotToElicit, 11 | message: { contentType: 'PlainText', content: message } 12 | }, 13 | }; 14 | } 15 | 16 | close({ message, sessionAttributes = {}, fulfillmentState = "Fulfilled" }) { 17 | return { 18 | sessionAttributes, 19 | dialogAction: { 20 | type: 'Close', 21 | fulfillmentState, 22 | message: { contentType: 'PlainText', content: message } 23 | } 24 | } 25 | } 26 | 27 | elicitIntent({ message, sessionAttributes = {} }) { 28 | return { 29 | sessionAttributes, 30 | dialogAction: { 31 | type: 'ElicitIntent', 32 | message: { contentType: 'PlainText', content: message } 33 | }, 34 | }; 35 | } 36 | 37 | confirmIntent({ sessionAttributes = {}, intentName, slots, message }) { 38 | return { 39 | sessionAttributes, 40 | dialogAction: { 41 | type: 'ConfirmIntent', 42 | intentName, 43 | slots, 44 | message: { contentType: 'PlainText', content: message } 45 | }, 46 | }; 47 | } 48 | 49 | delegate({ sessionAttributes = {}, slots }) { 50 | return { 51 | sessionAttributes, 52 | dialogAction: { 53 | type: 'Delegate', 54 | slots, 55 | }, 56 | }; 57 | } 58 | } -------------------------------------------------------------------------------- /Chapter06/getSavedCart/archive.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Hands-On-Chatbot-Development-with-Alexa-Skills-and-Amazon-Lex/a90bd11fcdb4c0c42b7d30094413e36106826d1f/Chapter06/getSavedCart/archive.zip -------------------------------------------------------------------------------- /Chapter06/getSavedCart/index.js: -------------------------------------------------------------------------------- 1 | const lex = require('./LexResponses'); 2 | const Lex = new lex(); 3 | const db = require('./DB'); 4 | const DB = new db(); 5 | 6 | exports.handler = async (event) => { 7 | return handleGetSavedCart(event); 8 | } 9 | 10 | const handleGetSavedCart = async event => { 11 | let { userId, currentIntent: { slots } } = event; 12 | let { cartName } = slots; 13 | 14 | if (!cartName) { 15 | let message = `What name did you save your cart as?`; 16 | let intentName = 'getSavedCart'; 17 | let slotToElicit = 'cartName'; 18 | let slots = { cartName: null }; 19 | return Lex.elicitSlot({ intentName, slots, slotToElicit, message }); 20 | } 21 | 22 | let [err, carts] = await to(DB.getDifferent('cartName', cartName, 'shopping-cart')); 23 | if (err || !carts || !carts[0]) { 24 | let message = `We couldn't find a cart with that name. Would you like to try another name or start a new cart?`; 25 | return Lex.elicitIntent({ message }); 26 | } 27 | let cart = carts[0]; 28 | 29 | let oldCartID = cart.ID; 30 | let newCart = { ...cart, ID: userId, TTL: Date.now() + 7 * 24 * 60 * 60 * 1000 }; 31 | try { 32 | await DB.write(userId, newCart, 'shopping-cart'); 33 | await DB.delete(oldCartID, 'shopping-cart'); 34 | } catch (createErr) { 35 | let message = `Unfortunately we couldn't recover your cart. Would you like to create a new cart?`; 36 | let intentName = 'productFind'; 37 | let slots = { type: null, size: null, colour: null, length: null, itemNumber: null }; 38 | return Lex.confirmIntent({ intentName, slots, message }); 39 | } 40 | 41 | let message = `We have got your cart for you. Would you like to checkout or add another product?`; 42 | return Lex.elicitIntent({ message }); 43 | } 44 | 45 | const to = prom => prom.then(res => [null, res]).catch(err => [err, null]); -------------------------------------------------------------------------------- /Chapter06/getSavedCart/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "lockfileVersion": 1 3 | } 4 | -------------------------------------------------------------------------------- /Chapter06/getSavedCart/tests: -------------------------------------------------------------------------------- 1 | { 2 | "currentIntent": { 3 | "slots": { 4 | "cartName": null 5 | } 6 | } 7 | } 8 | 9 | 10 | { 11 | "currentIntent": { 12 | "slots": { 13 | "cartName": "nonsense" 14 | } 15 | } 16 | } 17 | 18 | 19 | { 20 | "currentIntent": { 21 | "slots": { 22 | "cartName": "testCartSave" 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /Chapter06/productFind/LexResponses.js: -------------------------------------------------------------------------------- 1 | module.exports = class Lex { 2 | 3 | elicitSlot({ sessionAttributes = {}, message, intentName, slotToElicit, slots }) { 4 | return { 5 | sessionAttributes, 6 | dialogAction: { 7 | type: 'ElicitSlot', 8 | intentName, 9 | slots, 10 | slotToElicit, 11 | message: { contentType: 'PlainText', content: message } 12 | }, 13 | }; 14 | } 15 | 16 | close({ message, sessionAttributes = {}, fulfillmentState = "Fulfilled" }) { 17 | return { 18 | sessionAttributes, 19 | dialogAction: { 20 | type: 'Close', 21 | fulfillmentState, 22 | message: { contentType: 'PlainText', content: message } 23 | } 24 | } 25 | } 26 | 27 | elicitIntent({ message, sessionAttributes = {} }) { 28 | return { 29 | sessionAttributes, 30 | dialogAction: { 31 | type: 'ElicitIntent', 32 | message: { contentType: 'PlainText', content: message } 33 | }, 34 | }; 35 | } 36 | 37 | confirmIntent({ sessionAttributes = {}, intentName, slots, message }) { 38 | return { 39 | sessionAttributes, 40 | dialogAction: { 41 | type: 'ConfirmIntent', 42 | intentName, 43 | slots, 44 | message: { contentType: 'PlainText', content: message } 45 | }, 46 | }; 47 | } 48 | 49 | delegate({ sessionAttributes = {}, slots }) { 50 | return { 51 | sessionAttributes, 52 | dialogAction: { 53 | type: 'Delegate', 54 | slots, 55 | }, 56 | }; 57 | } 58 | } -------------------------------------------------------------------------------- /Chapter06/productFind/archive.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Hands-On-Chatbot-Development-with-Alexa-Skills-and-Amazon-Lex/a90bd11fcdb4c0c42b7d30094413e36106826d1f/Chapter06/productFind/archive.zip -------------------------------------------------------------------------------- /Chapter06/productFind/index.js: -------------------------------------------------------------------------------- 1 | const lex = require('./LexResponses'); 2 | const Lex = new lex(); 3 | const AWS = require('aws-sdk'); 4 | const s3 = new AWS.S3(); 5 | 6 | 7 | exports.handler = async (event) => { 8 | if (event.currentIntent && event.currentIntent.confirmationStatus) { 9 | let confirmationStatus = event.currentIntent.confirmationStatus; 10 | if (confirmationStatus == "Denied") { 11 | console.log('got denied status'); 12 | let message = `Thank you for shopping with us today. Have a nice day` 13 | return Lex.close({ message }) 14 | } 15 | if (confirmationStatus == 'Confirmed') { 16 | console.log('got confirmed status'); 17 | } 18 | } 19 | return handleProductFind(event); 20 | } 21 | 22 | const handleProductFind = event => { 23 | let { slots } = event.currentIntent; 24 | let { itemNumber, type, size, colour, length } = slots; 25 | 26 | if (itemNumber) return getItem(slots); 27 | // No item number so using normal product find 28 | if (!type) { 29 | let message = 'Are you looking for a shirt, jacket or trousers?'; 30 | let intentName = 'productFind'; 31 | let slotToElicit = 'type'; 32 | return Lex.elicitSlot({ message, intentName, slotToElicit, slots }) 33 | } 34 | if (!size) { 35 | let message = `What size of ${type} are you looking for?`; 36 | let intentName = 'productFind'; 37 | let slotToElicit = 'size'; 38 | return Lex.elicitSlot({ message, intentName, slotToElicit, slots }) 39 | } 40 | if (!colour) { 41 | let message = 'What colour would you like?'; 42 | let intentName = 'productFind'; 43 | let slotToElicit = 'colour'; 44 | return Lex.elicitSlot({ message, intentName, slotToElicit, slots }) 45 | } 46 | if (!length && type === 'trousers') { 47 | let message = 'Are you looking for short, standard or long trousers?'; 48 | let intentName = 'productFind'; 49 | let slotToElicit = 'length'; 50 | return Lex.elicitSlot({ message, intentName, slotToElicit, slots }) 51 | } 52 | 53 | return getItem(slots); 54 | } 55 | 56 | const getItem = async slots => { 57 | console.log('slots', slots); 58 | let { itemNumber, type, size, colour, length } = slots; 59 | let stock = await getStock(); 60 | let matching = stock.find(item => 61 | itemNumber === item.itemNumber || 62 | type == item.type && 63 | size == item.size && 64 | colour == item.colour && 65 | (item.length == length || item.type != 'trousers')); 66 | if (!matching) { 67 | let message = `Unfortunately we couldn't find the item you were looking for`; 68 | return Lex.close({ message }) 69 | } 70 | if (matching.stock < 1) { 71 | let message = `Unfortunately we don't have anything matching your request in stock. Would you like to search again?`; 72 | let intentName = 'productFind'; 73 | slots = { type: null, size: null, colour: null, length: null, itemNumber: null }; 74 | return Lex.confirmIntent({ intentName, slots, message }) 75 | } 76 | let message = `There are ${matching.stock} ${matching.colour} ${units(matching.type, matching.stock)} in stock. Would you like to add one to your basket?`; 77 | let intentName = 'addToCart'; 78 | slots = { itemNumber: matching.itemNumber }; 79 | return Lex.confirmIntent({ intentName, slots, message }); 80 | } 81 | 82 | const units = (type, stock) => { 83 | if (type === 'trousers') { 84 | return `pair${stock === 1 ? 's': ''} of trousers` 85 | } 86 | return `${type}${stock === 1 ? 's': ''}`; 87 | } 88 | 89 | const getStock = () => { 90 | var params = { 91 | Bucket: 'shopping-stock', 92 | Key: `stock.json` 93 | }; 94 | 95 | return new Promise((resolve, reject) => { 96 | s3.getObject(params, function(err, data) { 97 | if (err) { // an error occurred 98 | reject(err) 99 | } else { // successful response 100 | resolve(JSON.parse(data.Body).stock) 101 | } 102 | }); 103 | }) 104 | } -------------------------------------------------------------------------------- /Chapter06/productFind/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "lockfileVersion": 1 3 | } 4 | -------------------------------------------------------------------------------- /Chapter06/productFind/tests: -------------------------------------------------------------------------------- 1 | no slots 2 | 3 | { 4 | "currentIntent": { 5 | "slots": { 6 | "type": null, 7 | "size": null, 8 | "colour": null, 9 | "length": null, 10 | "itemNumber": null 11 | } 12 | } 13 | } 14 | 15 | all slots and confirmed 16 | 17 | { 18 | "currentIntent": { 19 | "slots": { 20 | "type": "shirt", 21 | "size": "medium", 22 | "colour": "blue", 23 | "length": null, 24 | "itemNumber": null 25 | }, 26 | "confirmationStatus": "Confirmed" 27 | } 28 | } 29 | 30 | Denied 31 | 32 | { 33 | "currentIntent": { 34 | "slots": { 35 | "type": "shirt", 36 | "size": "medium", 37 | "colour": "blue", 38 | "length": null, 39 | "itemNumber": null 40 | }, 41 | "confirmationStatus": "Denied" 42 | } 43 | } 44 | 45 | ItemNumber slot 46 | 47 | { 48 | "currentIntent": { 49 | "slots": { 50 | "type": null, 51 | "size": null, 52 | "colour": null, 53 | "length": null, 54 | "itemNumber": 1015 55 | } 56 | } 57 | } 58 | 59 | 60 | -------------------------------------------------------------------------------- /Chapter06/saveCart/DB.js: -------------------------------------------------------------------------------- 1 | const AWS = require('aws-sdk'); 2 | 3 | let documentClient = new AWS.DynamoDB.DocumentClient({ 4 | 'region': 'eu-west-1' 5 | }); 6 | 7 | module.exports = class DB { 8 | write(ID, data, table) { 9 | return new Promise((resolve, reject) => { 10 | if (!ID) throw 'An ID is needed'; 11 | if (typeof ID !== 'string') throw `the id must be a string and not ${ID}` 12 | if (!data) throw "data is needed"; 13 | if (!table) throw 'table name is needed'; 14 | if (typeof table !== 'string') throw `the table name must be a string and not ${table}` 15 | 16 | let params = { 17 | TableName: table, 18 | Item: { ...data, ID: ID } 19 | }; 20 | 21 | documentClient.put(params, function(err, result) { 22 | if (err) { 23 | console.log("Err in writeForCall writing messages to dynamo:", err); 24 | console.log(params); 25 | return reject(err); 26 | } 27 | console.log('wrote data to table ', table) 28 | return resolve({ ...result.Attributes, ...params.Item }); 29 | }); 30 | }) 31 | }; 32 | 33 | get(key, value, table) { 34 | if (!table) throw 'table needed'; 35 | if (typeof key !== 'string') throw `key was not string and was ${JSON.stringify(key)} on table ${table}`; 36 | if (typeof value !== 'string') throw `value was not string and was ${JSON.stringify(value)} on table ${table}`; 37 | if (!table) 'table needs to be users, sessions, or routes.' 38 | return new Promise((resolve, reject) => { 39 | let params = { 40 | TableName: table, 41 | Key: { 42 | [key]: value 43 | } 44 | }; 45 | documentClient.get(params, function(err, data) { 46 | if (err) { 47 | console.log(`There was an error fetching the data for ${key} ${value} on table ${table}`, err); 48 | return reject(err); 49 | } 50 | //TODO check only one Item. 51 | return resolve(data.Item); 52 | }); 53 | }); 54 | } 55 | 56 | async update(ID, table, key, value) { 57 | let data = await this.get(ID, table); 58 | return this.write(ID, { ...data, [key]: value }, table); 59 | } 60 | 61 | delete(ID, table) { 62 | if (!table) throw 'table needed'; 63 | if (typeof ID !== 'string') throw `ID was not string and was ${JSON.stringify(ID)} on table ${table}`; 64 | console.log("dynamo deleting record ID", ID, 'from table ', table); 65 | let params = { 66 | TableName: table, 67 | Key: { 68 | ID 69 | } 70 | }; 71 | 72 | return new Promise((resolve, reject) => { 73 | documentClient.delete(params, function(err, data) { 74 | if (err) { 75 | reject(err); 76 | } else { 77 | resolve(data); 78 | } 79 | }); 80 | }); 81 | 82 | } 83 | 84 | } -------------------------------------------------------------------------------- /Chapter06/saveCart/LexResponses.js: -------------------------------------------------------------------------------- 1 | module.exports = class Lex { 2 | 3 | elicitSlot({ sessionAttributes = {}, message, intentName, slotToElicit, slots }) { 4 | return { 5 | sessionAttributes, 6 | dialogAction: { 7 | type: 'ElicitSlot', 8 | intentName, 9 | slots, 10 | slotToElicit, 11 | message: { contentType: 'PlainText', content: message } 12 | }, 13 | }; 14 | } 15 | 16 | close({ message, sessionAttributes = {}, fulfillmentState = "Fulfilled" }) { 17 | return { 18 | sessionAttributes, 19 | dialogAction: { 20 | type: 'Close', 21 | fulfillmentState, 22 | message: { contentType: 'PlainText', content: message } 23 | } 24 | } 25 | } 26 | 27 | elicitIntent({ message, sessionAttributes = {} }) { 28 | return { 29 | sessionAttributes, 30 | dialogAction: { 31 | type: 'ElicitIntent', 32 | message: { contentType: 'PlainText', content: message } 33 | }, 34 | }; 35 | } 36 | 37 | confirmIntent({ sessionAttributes = {}, intentName, slots, message }) { 38 | return { 39 | sessionAttributes, 40 | dialogAction: { 41 | type: 'ConfirmIntent', 42 | intentName, 43 | slots, 44 | message: { contentType: 'PlainText', content: message } 45 | }, 46 | }; 47 | } 48 | 49 | delegate({ sessionAttributes = {}, slots }) { 50 | return { 51 | sessionAttributes, 52 | dialogAction: { 53 | type: 'Delegate', 54 | slots, 55 | }, 56 | }; 57 | } 58 | } -------------------------------------------------------------------------------- /Chapter06/saveCart/index.js: -------------------------------------------------------------------------------- 1 | const lex = require('./LexResponses'); 2 | const Lex = new lex(); 3 | const db = require('./DB'); 4 | const DB = new db(); 5 | 6 | exports.handler = async (event) => { 7 | return handleSaveCart(event); 8 | } 9 | 10 | const handleSaveCart = async event => { 11 | let { slots } = event.currentIntent; 12 | let { cartName } = slots; 13 | 14 | if (!cartName) { 15 | let message = `You need to save your cart with a name. What do you want to call it?`; 16 | let intentName = 'saveCart'; 17 | slots = { cartName: null }; 18 | let slotToElicit = 'cartName'; 19 | return Lex.elicitSlot({ intentName, slotToElicit, slots, message }); 20 | } 21 | 22 | let [err, cart] = await to(DB.get('ID', event.userId, 'shopping-cart')); 23 | if (err || !cart || !cart.Items) { 24 | let message = `You don't have a cart. Would you like to find a product?`; 25 | let intentName = 'productFind'; 26 | slots = { type: null, size: null, colour: null, length: null, itemNumber: null }; 27 | return Lex.confirmIntent({ intentName, slots, message }); 28 | } 29 | 30 | let [getCartErr, getCart] = await to(DB.get('name', cartName, 'shopping-cart')); 31 | if (!getCart) { 32 | // No cart with that name so we can save the current cart to this name 33 | return addNameToCart(cart, cartName); 34 | } 35 | let message = `Unfortunately you can't use that name. Please choose another name.`; 36 | let intentName = 'saveCart'; 37 | let slotToElicit = 'cartName'; 38 | slots = { cartName: null }; 39 | return Lex.elicitSlot({ intentName, slots, slotToElicit, message }); 40 | } 41 | 42 | const addNameToCart = async (cart, cartName) => { 43 | cart.name = cartName; 44 | let [err, res] = await to(DB.write(cart.ID, cart, 'shopping-cart')); 45 | if (err) { 46 | console.log('err writing cart with name', err); 47 | let message = `Unfortunately we cant save your cart`; 48 | return Lex.close({ message }); 49 | } 50 | let message = `Your cart has been saved. Type "find my cart" next time and enter "${cartName}" to get this cart.`; 51 | return Lex.close({ message }); 52 | } 53 | 54 | const to = prom => prom.then(res => [null, res]).catch(err => [err, null]); -------------------------------------------------------------------------------- /Chapter06/saveCart/tests: -------------------------------------------------------------------------------- 1 | { 2 | "currentIntent": { 3 | "slots": { 4 | "cartName": null 5 | } 6 | } 7 | } 8 | 9 | 10 | { 11 | "currentIntent": { 12 | "slots": { 13 | "cartName": "personalShopping" 14 | } 15 | }, 16 | "userId": "asdasdasdasdasd" 17 | } 18 | 19 | 20 | { 21 | "currentIntent": { 22 | "slots": { 23 | "cartName": "testCartSave" 24 | } 25 | }, 26 | "userId": ## valid userId 27 | } 28 | 29 | 30 | { 31 | "currentIntent": { 32 | "slots": { 33 | "cartName": "testCartSave" 34 | } 35 | }, 36 | "userId": ## another valid userId 37 | } -------------------------------------------------------------------------------- /Chapter06/whatsInMyCart/DB.js: -------------------------------------------------------------------------------- 1 | const AWS = require('aws-sdk'); 2 | 3 | let documentClient = new AWS.DynamoDB.DocumentClient({ 4 | 'region': 'eu-west-1' 5 | }); 6 | 7 | module.exports = class DB { 8 | write(ID, data, table) { 9 | return new Promise((resolve, reject) => { 10 | if (!ID) throw 'An ID is needed'; 11 | if (typeof ID !== 'string') throw `the id must be a string and not ${ID}` 12 | if (!data) throw "data is needed"; 13 | if (!table) throw 'table name is needed'; 14 | if (typeof table !== 'string') throw `the table name must be a string and not ${table}` 15 | 16 | let params = { 17 | TableName: table, 18 | Item: { ...data, ID: ID } 19 | }; 20 | 21 | documentClient.put(params, function(err, result) { 22 | if (err) { 23 | console.log("Err in writeForCall writing messages to dynamo:", err); 24 | console.log(params); 25 | return reject(err); 26 | } 27 | console.log('wrote data to table ', table) 28 | return resolve({ ...result.Attributes, ...params.Item }); 29 | }); 30 | }) 31 | }; 32 | 33 | get(key, value, table) { 34 | if (!table) throw 'table needed'; 35 | if (typeof key !== 'string') throw `key was not string and was ${JSON.stringify(key)} on table ${table}`; 36 | if (typeof value !== 'string') throw `value was not string and was ${JSON.stringify(value)} on table ${table}`; 37 | if (!table) 'table needs to be users, sessions, or routes.' 38 | return new Promise((resolve, reject) => { 39 | let params = { 40 | TableName: table, 41 | Key: { 42 | [key]: value 43 | } 44 | }; 45 | documentClient.get(params, function(err, data) { 46 | if (err) { 47 | console.log(`There was an error fetching the data for ${key} ${value} on table ${table}`, err); 48 | return reject(err); 49 | } 50 | //TODO check only one Item. 51 | return resolve(data.Item); 52 | }); 53 | }); 54 | } 55 | 56 | getDifferent(key, value, table) { 57 | if (!table) throw 'table needed'; 58 | if (typeof key !== 'string') throw `key was not string and was ${JSON.stringify(key)} on table ${table}`; 59 | if (typeof value !== 'string') throw `value was not string and was ${JSON.stringify(value)} on table ${table}`; 60 | if (!table) 'table needs to be users, sessions, or routes.' 61 | return new Promise((resolve, reject) => { 62 | var params = { 63 | TableName: table, 64 | IndexName: `${key}-index`, 65 | KeyConditionExpression: `${key} = :value`, 66 | ExpressionAttributeValues: { 67 | ':value': value 68 | } 69 | }; 70 | 71 | documentClient.query(params, function(err, data) { 72 | if (err) { 73 | console.error("Unable to read item. Error JSON:", JSON.stringify(err)); 74 | reject(err); 75 | } else { 76 | console.log("GetItem succeeded:", JSON.stringify(data.Items)); 77 | resolve(data.Items); 78 | } 79 | }); 80 | }) 81 | } 82 | 83 | async update(ID, table, key, value) { 84 | let data = await this.get(ID, table); 85 | return this.write(ID, { ...data, [key]: value }, table); 86 | } 87 | 88 | delete(ID, table) { 89 | if (!table) throw 'table needed'; 90 | if (typeof ID !== 'string') throw `ID was not string and was ${JSON.stringify(ID)} on table ${table}`; 91 | console.log("dynamo deleting record ID", ID, 'from table ', table); 92 | let params = { 93 | TableName: table, 94 | Key: { 95 | ID 96 | } 97 | }; 98 | 99 | return new Promise((resolve, reject) => { 100 | documentClient.delete(params, function(err, data) { 101 | if (err) { 102 | reject(err); 103 | } else { 104 | resolve(data); 105 | } 106 | }); 107 | }); 108 | 109 | } 110 | 111 | } -------------------------------------------------------------------------------- /Chapter06/whatsInMyCart/LexResponses.js: -------------------------------------------------------------------------------- 1 | module.exports = class Lex { 2 | 3 | elicitSlot({ sessionAttributes = {}, message, intentName, slotToElicit, slots }) { 4 | return { 5 | sessionAttributes, 6 | dialogAction: { 7 | type: 'ElicitSlot', 8 | intentName, 9 | slots, 10 | slotToElicit, 11 | message: { contentType: 'PlainText', content: message } 12 | }, 13 | }; 14 | } 15 | 16 | close({ message, sessionAttributes = {}, fulfillmentState = "Fulfilled" }) { 17 | return { 18 | sessionAttributes, 19 | dialogAction: { 20 | type: 'Close', 21 | fulfillmentState, 22 | message: { contentType: 'PlainText', content: message } 23 | } 24 | } 25 | } 26 | 27 | elicitIntent({ message, sessionAttributes = {} }) { 28 | return { 29 | sessionAttributes, 30 | dialogAction: { 31 | type: 'ElicitIntent', 32 | message: { contentType: 'PlainText', content: message } 33 | }, 34 | }; 35 | } 36 | 37 | confirmIntent({ sessionAttributes = {}, intentName, slots, message }) { 38 | return { 39 | sessionAttributes, 40 | dialogAction: { 41 | type: 'ConfirmIntent', 42 | intentName, 43 | slots, 44 | message: { contentType: 'PlainText', content: message } 45 | }, 46 | }; 47 | } 48 | 49 | delegate({ sessionAttributes = {}, slots }) { 50 | return { 51 | sessionAttributes, 52 | dialogAction: { 53 | type: 'Delegate', 54 | slots, 55 | }, 56 | }; 57 | } 58 | } -------------------------------------------------------------------------------- /Chapter06/whatsInMyCart/archive.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Hands-On-Chatbot-Development-with-Alexa-Skills-and-Amazon-Lex/a90bd11fcdb4c0c42b7d30094413e36106826d1f/Chapter06/whatsInMyCart/archive.zip -------------------------------------------------------------------------------- /Chapter06/whatsInMyCart/index.js: -------------------------------------------------------------------------------- 1 | const lex = require('./LexResponses'); 2 | const Lex = new lex(); 3 | const db = require('./DB'); 4 | const DB = new db(); 5 | const AWS = require('aws-sdk'); 6 | const s3 = new AWS.S3(); 7 | 8 | exports.handler = async (event) => { 9 | return handleWhatsInMyCart(event); 10 | } 11 | 12 | const handleWhatsInMyCart = async event => { 13 | let [err, cart] = await to(DB.get('ID', event.userId, 'shopping-cart')); 14 | if (err || !cart || cart.Items.length == 0) { 15 | let message = `You don't appear to have a cart. If you have saved a cart then you can recover it by typing "Get my cart", or you can say "I want to buy something"`; 16 | return Lex.elicitIntent({ message }); 17 | } 18 | 19 | let items = {}; 20 | cart.Items.forEach(item => { 21 | items[item] = (items[item] && items[item].quantity) ? { quantity: items[item].quantity + 1 } : { quantity: 1 }; 22 | }); 23 | 24 | const [s3Err, products] = await to(getStock()); 25 | if (s3Err || !products) { 26 | let message = `Unfortunately our system has had an error.`; 27 | Lex.close({ message }); 28 | } 29 | 30 | products.forEach(product => { 31 | if (items[product.itemNumber]) { 32 | items[product.itemNumber] = { ...product, ...items[product.itemNumber] }; 33 | } 34 | }); 35 | 36 | let itemStrings = Object.values(items).map(item => { 37 | let { type, size, colour, length, quantity } = item; 38 | return `${quantity} ${size}, ${length ? `${length}, ` : ''}${colour} ${units(type, quantity)}`; 39 | }); 40 | 41 | let message = `You have ${itemStrings.slice(0,-1).join(', ')}${itemStrings.length > 1 ? ` and `: ""}${itemStrings.pop()} in your cart. Would you like to checkout, save your cart or add another item?`; 42 | return Lex.elicitIntent({ message }); 43 | } 44 | 45 | const units = (type, stock) => { 46 | if (type === 'trousers') { 47 | return `pair${stock === 1 ? 's': ''} of trousers` 48 | } 49 | return `${type}${stock === 1 ? 's': ''}`; 50 | } 51 | 52 | const getStock = () => { 53 | var params = { 54 | Bucket: 'shopping-stock', 55 | Key: `stock.json` 56 | }; 57 | 58 | return new Promise((resolve, reject) => { 59 | s3.getObject(params, function(err, data) { 60 | if (err) { // an error occurred 61 | reject(err) 62 | } else { // successful response 63 | resolve(JSON.parse(data.Body).stock) 64 | } 65 | }); 66 | }) 67 | } 68 | 69 | const to = prom => prom.then(res => [null, res]).catch(err => [err, null]); -------------------------------------------------------------------------------- /Chapter06/whatsInMyCart/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "lockfileVersion": 1 3 | } 4 | -------------------------------------------------------------------------------- /Chapter06/whatsInMyCart/tests: -------------------------------------------------------------------------------- 1 | { 2 | "userId": "nonsense" 3 | } 4 | 5 | 6 | 7 | { 8 | "userId": ## valid userID 9 | } -------------------------------------------------------------------------------- /Chapter07/chatbotUI/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 |
11 |
12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /Chapter07/chatbotUI/script.js: -------------------------------------------------------------------------------- 1 | if (document.readyState === 'complete') { 2 | start(); 3 | } else { 4 | document.addEventListener("DOMContentLoaded", start()) 5 | } 6 | 7 | function to(prom) { 8 | return prom.then(res => [null, res]).catch(err => [err, null]) 9 | } 10 | 11 | function start() { 12 | 13 | const URL = 'https://8hzzu5mwnk.execute-api.eu-west-1.amazonaws.com/production/shopping-bot'; 14 | // unique code for this session 15 | const sessionID = Math.random().toString().slice(-16); 16 | 17 | let messageArea = document.querySelector('#messageArea'); 18 | let textArea = document.querySelector('#textInput'); 19 | let sendButton = document.querySelector('#sendButton'); 20 | 21 | 22 | sendButton.addEventListener('click', async e => { 23 | let text = textArea.value; 24 | console.log(text); 25 | if (!text) return; 26 | // Add to sent messages 27 | let sendElement = document.createElement('div'); 28 | sendElement.classList.add('sendMessage'); 29 | sendElement.classList.add('message'); 30 | sendElement.appendChild(document.createTextNode(text)); 31 | messageArea.appendChild(sendElement); 32 | 33 | // send to the API 34 | let [err, response] = await to(axios.post(URL, { text, sessionID })); 35 | 36 | let responseMessage; 37 | if (err) { 38 | responseMessage = 'Sorry I appear to have had an error'; 39 | } else { 40 | responseMessage = response.data.message; 41 | } 42 | 43 | // adding the response to received messages 44 | let receiveElement = document.createElement('div'); 45 | receiveElement.classList.add('receivedMessage'); 46 | receiveElement.classList.add('message'); 47 | receiveElement.appendChild(document.createTextNode(responseMessage)); 48 | messageArea.appendChild(receiveElement); 49 | 50 | }); 51 | }; -------------------------------------------------------------------------------- /Chapter07/chatbotUI/style.css: -------------------------------------------------------------------------------- 1 | #messageArea { 2 | background: #eee; 3 | height: 93vh; 4 | max-width: 450px; 5 | overflow-y: scroll; 6 | } 7 | 8 | .message { 9 | padding: 3%; 10 | margin: 2% 11 | } 12 | 13 | .sendMessage { 14 | right: -20%; 15 | position: relative; 16 | max-width: 70%; 17 | background: blue; 18 | color: white; 19 | border-radius: 16px 16px 8px 16px 20 | } 21 | 22 | .receivedMessage { 23 | background: #bbb; 24 | position: relative; 25 | left: 0; 26 | max-width: 70%; 27 | border-radius: 16px 16px 16px 8px; 28 | } 29 | 30 | #inputDivs { 31 | width: 450px; 32 | display: flex; 33 | } 34 | 35 | #textInput { 36 | font-size: 15px; 37 | flex-grow: 2; 38 | } 39 | 40 | #sendButton { 41 | font-size: 15px; 42 | border: 0px solid lightskyblue; 43 | background: lightskyblue; 44 | border-radius: 8px; 45 | padding: 8px; 46 | margin-left: 8px; 47 | } -------------------------------------------------------------------------------- /Chapter07/lex-shopping-api/archive.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Hands-On-Chatbot-Development-with-Alexa-Skills-and-Amazon-Lex/a90bd11fcdb4c0c42b7d30094413e36106826d1f/Chapter07/lex-shopping-api/archive.zip -------------------------------------------------------------------------------- /Chapter07/lex-shopping-api/index.js: -------------------------------------------------------------------------------- 1 | const AWS = require('aws-sdk'); 2 | const lexruntime = new AWS.LexRuntime(); 3 | 4 | 5 | exports.handler = async (event) => { 6 | 7 | if (event.httpMethod === "POST") { 8 | let reply = await sendToLex(event); 9 | return done(reply); 10 | } 11 | }; 12 | 13 | const sendToLex = async event => { 14 | console.log('event', event); 15 | let messageForLex = mapMessageToLex(JSON.parse(event.body)); 16 | 17 | let lexPromise = new Promise((resolve, reject) => { 18 | lexruntime.postText(messageForLex, (err, data) => { 19 | if (err) { 20 | reject(err); 21 | } else { 22 | resolve(data); 23 | } 24 | }) 25 | }); 26 | 27 | let [err, res] = await to(lexPromise); 28 | if (err) { 29 | return { err } 30 | } 31 | console.log('lex response', res); 32 | return { res: { message: res.message } } 33 | } 34 | 35 | const mapMessageToLex = message => { 36 | return { 37 | botAlias: 'prod', 38 | botName: 'shoppingBot', 39 | inputText: message.text, 40 | userId: message.sessionID, 41 | sessionAttributes: {} 42 | }; 43 | } 44 | 45 | const to = prom => prom.then(res => [null, res]).catch(err => [err, null]); 46 | 47 | const done = ({ err, res }) => { 48 | console.log('res', res); 49 | console.log('error', err); 50 | return { 51 | statusCode: err ? '404' : '200', 52 | body: err ? JSON.stringify({ error: err }) : JSON.stringify(res), 53 | headers: { 54 | 'Content-Type': 'application/json', 55 | 'Access-Control-Allow-Methods': '*', 56 | 'Access-Control-Allow-Origin': '*' 57 | }, 58 | }; 59 | } -------------------------------------------------------------------------------- /Chapter07/lex-shopping-api/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "lockfileVersion": 1 3 | } 4 | -------------------------------------------------------------------------------- /Chapter08/data/stock.json: -------------------------------------------------------------------------------- 1 | { 2 | "stock": [ 3 | { "itemNumber": 1000, "type": "trousers", "size": "small", "imageUrl": "https://asda.scene7.com/is/image/Asda/5053516027882?hei=1800&qlt=85&fmt=pjpg&resmode=sharp&op_usm=1.1,0.5,0,0&defaultimage=default_details_George_rd", "colour": "black", "length": "long", "stock": 3 }, 4 | { "itemNumber": 1001, "type": "trousers", "size": "small", "imageUrl": "https://asda.scene7.com/is/image/Asda/5053516027882?hei=1800&qlt=85&fmt=pjpg&resmode=sharp&op_usm=1.1,0.5,0,0&defaultimage=default_details_George_rd", "colour": "black", "length": "standard", "stock": 5 }, 5 | { "itemNumber": 1002, "type": "trousers", "size": "small", "imageUrl": "https://asda.scene7.com/is/image/Asda/5053516027882?hei=1800&qlt=85&fmt=pjpg&resmode=sharp&op_usm=1.1,0.5,0,0&defaultimage=default_details_George_rd", "colour": "black", "length": "short", "stock": 0 }, 6 | { "itemNumber": 1003, "type": "trousers", "size": "medium", "imageUrl": "https://asda.scene7.com/is/image/Asda/5053516027882?hei=1800&qlt=85&fmt=pjpg&resmode=sharp&op_usm=1.1,0.5,0,0&defaultimage=default_details_George_rd", "colour": "black", "length": "long", "stock": 2 }, 7 | { "itemNumber": 1004, "type": "trousers", "size": "medium", "imageUrl": "https://asda.scene7.com/is/image/Asda/5053516027882?hei=1800&qlt=85&fmt=pjpg&resmode=sharp&op_usm=1.1,0.5,0,0&defaultimage=default_details_George_rd", "colour": "black", "length": "standard", "stock": 8 }, 8 | { "itemNumber": 1005, "type": "trousers", "size": "medium", "imageUrl": "https://asda.scene7.com/is/image/Asda/5053516027882?hei=1800&qlt=85&fmt=pjpg&resmode=sharp&op_usm=1.1,0.5,0,0&defaultimage=default_details_George_rd", "colour": "black", "length": "short", "stock": 4 }, 9 | { "itemNumber": 1006, "type": "trousers", "size": "large", "imageUrl": "https://asda.scene7.com/is/image/Asda/5053516027882?hei=1800&qlt=85&fmt=pjpg&resmode=sharp&op_usm=1.1,0.5,0,0&defaultimage=default_details_George_rd", "colour": "black", "length": "long", "stock": 4 }, 10 | { "itemNumber": 1007, "type": "trousers", "size": "large", "imageUrl": "https://asda.scene7.com/is/image/Asda/5053516027882?hei=1800&qlt=85&fmt=pjpg&resmode=sharp&op_usm=1.1,0.5,0,0&defaultimage=default_details_George_rd", "colour": "black", "length": "standard", "stock": 4 }, 11 | { "itemNumber": 1008, "type": "trousers", "size": "large", "imageUrl": "https://asda.scene7.com/is/image/Asda/5053516027882?hei=1800&qlt=85&fmt=pjpg&resmode=sharp&op_usm=1.1,0.5,0,0&defaultimage=default_details_George_rd", "colour": "black", "length": "short", "stock": 1 }, 12 | 13 | { "itemNumber": 1009, "type": "shirt", "size": "small", "imageUrl": "https://images-na.ssl-images-amazon.com/images/I/515GMvFiBFL._UX385_.jpg", "colour": "black", "stock": 6 }, 14 | { "itemNumber": 1010, "type": "shirt", "size": "medium", "imageUrl": "https://images-na.ssl-images-amazon.com/images/I/515GMvFiBFL._UX385_.jpg", "colour": "black", "stock": 0 }, 15 | { "itemNumber": 1011, "type": "shirt", "size": "large", "imageUrl": "https://images-na.ssl-images-amazon.com/images/I/515GMvFiBFL._UX385_.jpg", "colour": "black", "stock": 3 }, 16 | 17 | { "itemNumber": 1012, "type": "jacket", "size": "small", "imageUrl": "https://www.urbanrider.co.uk/media/catalog/product/cache/1/thumbnail/9df78eab33525d08d6e5fb8d27136e95/r/o/rokker-black-denim-jacket-front.jpg", "colour": "black", "stock": 4 }, 18 | { "itemNumber": 1013, "type": "jacket", "size": "medium", "imageUrl": "https://www.urbanrider.co.uk/media/catalog/product/cache/1/thumbnail/9df78eab33525d08d6e5fb8d27136e95/r/o/rokker-black-denim-jacket-front.jpg", "colour": "black", "stock": 6 }, 19 | { "itemNumber": 1014, "type": "jacket", "size": "large", "imageUrl": "https://www.urbanrider.co.uk/media/catalog/product/cache/1/thumbnail/9df78eab33525d08d6e5fb8d27136e95/r/o/rokker-black-denim-jacket-front.jpg", "colour": "black", "stock": 8 }, 20 | 21 | 22 | { "itemNumber": 1015, "type": "trousers", "size": "small", "imageUrl": "https://images-na.ssl-images-amazon.com/images/I/21Q40n%2B65iL.jpg", "colour": "white", "length": "long", "stock": 0 }, 23 | { "itemNumber": 1016, "type": "trousers", "size": "small", "imageUrl": "https://images-na.ssl-images-amazon.com/images/I/21Q40n%2B65iL.jpg", "colour": "white", "length": "standard", "stock": 1 }, 24 | { "itemNumber": 1017, "type": "trousers", "size": "small", "imageUrl": "https://images-na.ssl-images-amazon.com/images/I/21Q40n%2B65iL.jpg", "colour": "white", "length": "short", "stock": 2 }, 25 | { "itemNumber": 1018, "type": "trousers", "size": "medium", "imageUrl": "https://images-na.ssl-images-amazon.com/images/I/21Q40n%2B65iL.jpg", "colour": "white", "length": "long", "stock": 2 }, 26 | { "itemNumber": 1019, "type": "trousers", "size": "medium", "imageUrl": "https://images-na.ssl-images-amazon.com/images/I/21Q40n%2B65iL.jpg", "colour": "white", "length": "standard", "stock": 1 }, 27 | { "itemNumber": 1020, "type": "trousers", "size": "medium", "imageUrl": "https://images-na.ssl-images-amazon.com/images/I/21Q40n%2B65iL.jpg", "colour": "white", "length": "short", "stock": 0 }, 28 | { "itemNumber": 1021, "type": "trousers", "size": "large", "imageUrl": "https://images-na.ssl-images-amazon.com/images/I/21Q40n%2B65iL.jpg", "colour": "white", "length": "long", "stock": 0 }, 29 | { "itemNumber": 1022, "type": "trousers", "size": "large", "imageUrl": "https://images-na.ssl-images-amazon.com/images/I/21Q40n%2B65iL.jpg", "colour": "white", "length": "standard", "stock": 3 }, 30 | { "itemNumber": 1023, "type": "trousers", "size": "large", "imageUrl": "https://images-na.ssl-images-amazon.com/images/I/21Q40n%2B65iL.jpg", "colour": "white", "length": "short", "stock": 1 }, 31 | 32 | { "itemNumber": 1024, "type": "shirt", "size": "small", "imageUrl": "https://cdn.shopify.com/s/files/1/0407/0829/products/White_dress_shirt_front_1024x1024.jpg?v=1511411268", "colour": "white", "stock": 14 }, 33 | { "itemNumber": 1025, "type": "shirt", "size": "medium", "imageUrl": "https://cdn.shopify.com/s/files/1/0407/0829/products/White_dress_shirt_front_1024x1024.jpg?v=1511411268", "colour": "white", "stock": 20 }, 34 | { "itemNumber": 1026, "type": "shirt", "size": "large", "imageUrl": "https://cdn.shopify.com/s/files/1/0407/0829/products/White_dress_shirt_front_1024x1024.jpg?v=1511411268", "colour": "white", "stock": 13 }, 35 | 36 | { "itemNumber": 1027, "type": "jacket", "size": "small", "imageUrl": "https://cdnc.lystit.com/photos/5c62-2014/07/08/enfants-riches-deprimes-white-chessboard-bomber-jacket-casual-jackets-product-1-21454922-3-059641380-normal.jpeg", "colour": "white", "stock": 0 }, 37 | { "itemNumber": 1028, "type": "jacket", "size": "medium", "imageUrl": "https://cdnc.lystit.com/photos/5c62-2014/07/08/enfants-riches-deprimes-white-chessboard-bomber-jacket-casual-jackets-product-1-21454922-3-059641380-normal.jpeg", "colour": "white", "stock": 2 }, 38 | { "itemNumber": 1029, "type": "jacket", "size": "large", "imageUrl": "https://cdnc.lystit.com/photos/5c62-2014/07/08/enfants-riches-deprimes-white-chessboard-bomber-jacket-casual-jackets-product-1-21454922-3-059641380-normal.jpeg", "colour": "white", "stock": 2 }, 39 | 40 | 41 | { "itemNumber": 1030, "type": "trousers", "size": "small", "imageUrl": "https://www.decathlon.co.uk/media/839/8398185/big_1ecc4125-1113-4a19-ba61-591a418c9eda.jpg", "colour": "blue", "length": "long", "stock": 4 }, 42 | { "itemNumber": 1031, "type": "trousers", "size": "small", "imageUrl": "https://www.decathlon.co.uk/media/839/8398185/big_1ecc4125-1113-4a19-ba61-591a418c9eda.jpg", "colour": "blue", "length": "standard", "stock": 4 }, 43 | { "itemNumber": 1032, "type": "trousers", "size": "small", "imageUrl": "https://www.decathlon.co.uk/media/839/8398185/big_1ecc4125-1113-4a19-ba61-591a418c9eda.jpg", "colour": "blue", "length": "short", "stock": 3 }, 44 | { "itemNumber": 1033, "type": "trousers", "size": "medium", "imageUrl": "https://www.decathlon.co.uk/media/839/8398185/big_1ecc4125-1113-4a19-ba61-591a418c9eda.jpg", "colour": "blue", "length": "long", "stock": 3 }, 45 | { "itemNumber": 1034, "type": "trousers", "size": "medium", "imageUrl": "https://www.decathlon.co.uk/media/839/8398185/big_1ecc4125-1113-4a19-ba61-591a418c9eda.jpg", "colour": "blue", "length": "standard", "stock": 7 }, 46 | { "itemNumber": 1035, "type": "trousers", "size": "medium", "imageUrl": "https://www.decathlon.co.uk/media/839/8398185/big_1ecc4125-1113-4a19-ba61-591a418c9eda.jpg", "colour": "blue", "length": "short", "stock": 5 }, 47 | { "itemNumber": 1036, "type": "trousers", "size": "large", "imageUrl": "https://www.decathlon.co.uk/media/839/8398185/big_1ecc4125-1113-4a19-ba61-591a418c9eda.jpg", "colour": "blue", "length": "long", "stock": 3 }, 48 | { "itemNumber": 1037, "type": "trousers", "size": "large", "imageUrl": "https://www.decathlon.co.uk/media/839/8398185/big_1ecc4125-1113-4a19-ba61-591a418c9eda.jpg", "colour": "blue", "length": "standard", "stock": 1 }, 49 | { "itemNumber": 1038, "type": "trousers", "size": "large", "imageUrl": "https://www.decathlon.co.uk/media/839/8398185/big_1ecc4125-1113-4a19-ba61-591a418c9eda.jpg", "colour": "blue", "length": "short", "stock": 2 }, 50 | 51 | { "itemNumber": 1039, "type": "shirt", "size": "small", "imageUrl": "https://rukminim1.flixcart.com/image/832/832/shirt/s/h/y/46-bfrybluesht02-being-fab-original-imaekjr8ymhnxznp.jpeg?q=70", "colour": "blue", "stock": 7 }, 52 | { "itemNumber": 1040, "type": "shirt", "size": "medium", "imageUrl": "https://rukminim1.flixcart.com/image/832/832/shirt/s/h/y/46-bfrybluesht02-being-fab-original-imaekjr8ymhnxznp.jpeg?q=70", "colour": "blue", "stock": 8 }, 53 | { "itemNumber": 1041, "type": "shirt", "size": "large", "imageUrl": "https://rukminim1.flixcart.com/image/832/832/shirt/s/h/y/46-bfrybluesht02-being-fab-original-imaekjr8ymhnxznp.jpeg?q=70", "colour": "blue", "stock": 4 }, 54 | 55 | { "itemNumber": 1042, "type": "jacket", "size": "small", "imageUrl": "https://www.caineleather.co.uk/images/P/tr_brownston_blue_leather_jacket-01.jpg", "colour": "blue", "stock": 1 }, 56 | { "itemNumber": 1043, "type": "jacket", "size": "medium", "imageUrl": "https://www.caineleather.co.uk/images/P/tr_brownston_blue_leather_jacket-01.jpg", "colour": "blue", "stock": 5 }, 57 | { "itemNumber": 1044, "type": "jacket", "size": "large", "imageUrl": "https://www.caineleather.co.uk/images/P/tr_brownston_blue_leather_jacket-01.jpg", "colour": "blue", "stock": 8 }, 58 | 59 | 60 | { "itemNumber": 1045, "type": "trousers", "size": "small", "imageUrl": "https://www.hobbs.co.uk/images/products/0217-8602-9845L00-1945/0217-8602-9845L00-1945_01_1008_1080.jpg", "colour": "red", "length": "long", "stock": 0 }, 61 | { "itemNumber": 1046, "type": "trousers", "size": "small", "imageUrl": "https://www.hobbs.co.uk/images/products/0217-8602-9845L00-1945/0217-8602-9845L00-1945_01_1008_1080.jpg", "colour": "red", "length": "standard", "stock": 1 }, 62 | { "itemNumber": 1047, "type": "trousers", "size": "small", "imageUrl": "https://www.hobbs.co.uk/images/products/0217-8602-9845L00-1945/0217-8602-9845L00-1945_01_1008_1080.jpg", "colour": "red", "length": "short", "stock": 0 }, 63 | { "itemNumber": 1048, "type": "trousers", "size": "medium", "imageUrl": "https://www.hobbs.co.uk/images/products/0217-8602-9845L00-1945/0217-8602-9845L00-1945_01_1008_1080.jpg", "colour": "red", "length": "long", "stock": 1 }, 64 | { "itemNumber": 1049, "type": "trousers", "size": "medium", "imageUrl": "https://www.hobbs.co.uk/images/products/0217-8602-9845L00-1945/0217-8602-9845L00-1945_01_1008_1080.jpg", "colour": "red", "length": "standard", "stock": 2 }, 65 | { "itemNumber": 1050, "type": "trousers", "size": "medium", "imageUrl": "https://www.hobbs.co.uk/images/products/0217-8602-9845L00-1945/0217-8602-9845L00-1945_01_1008_1080.jpg", "colour": "red", "length": "short", "stock": 0 }, 66 | { "itemNumber": 1051, "type": "trousers", "size": "large", "imageUrl": "https://www.hobbs.co.uk/images/products/0217-8602-9845L00-1945/0217-8602-9845L00-1945_01_1008_1080.jpg", "colour": "red", "length": "long", "stock": 1 }, 67 | { "itemNumber": 1052, "type": "trousers", "size": "large", "imageUrl": "https://www.hobbs.co.uk/images/products/0217-8602-9845L00-1945/0217-8602-9845L00-1945_01_1008_1080.jpg", "colour": "red", "length": "standard", "stock": 0 }, 68 | { "itemNumber": 1053, "type": "trousers", "size": "large", "imageUrl": "https://www.hobbs.co.uk/images/products/0217-8602-9845L00-1945/0217-8602-9845L00-1945_01_1008_1080.jpg", "colour": "red", "length": "short", "stock": 2 }, 69 | 70 | { "itemNumber": 1054, "type": "shirt", "size": "small", "imageUrl": "http://christainapparel.com/wp-content/uploads/2017/04/plain-t-shirt-red-1-800x800.jpg", "colour": "red", "stock": 5 }, 71 | { "itemNumber": 1055, "type": "shirt", "size": "medium", "imageUrl": "http://christainapparel.com/wp-content/uploads/2017/04/plain-t-shirt-red-1-800x800.jpg", "colour": "red", "stock": 8 }, 72 | { "itemNumber": 1056, "type": "shirt", "size": "large", "imageUrl": "http://christainapparel.com/wp-content/uploads/2017/04/plain-t-shirt-red-1-800x800.jpg", "colour": "red", "stock": 4 }, 73 | 74 | { "itemNumber": 1057, "type": "jacket", "size": "small", "imageUrl": "https://www.jacketsmaker.com/wp-content/uploads/2016/01/The-Karate-Kid-Part-2-Johnny-s-William-Zabka-Cobra-Kai-Jacket-1-800x980.jpg", "colour": "red", "stock": 2 }, 75 | { "itemNumber": 1058, "type": "jacket", "size": "medium", "imageUrl": "https://www.jacketsmaker.com/wp-content/uploads/2016/01/The-Karate-Kid-Part-2-Johnny-s-William-Zabka-Cobra-Kai-Jacket-1-800x980.jpg", "colour": "red", "stock": 4 }, 76 | { "itemNumber": 1059, "type": "jacket", "size": "large", "imageUrl": "https://www.jacketsmaker.com/wp-content/uploads/2016/01/The-Karate-Kid-Part-2-Johnny-s-William-Zabka-Cobra-Kai-Jacket-1-800x980.jpg", "colour": "red", "stock": 5 }, 77 | 78 | 79 | { "itemNumber": 1060, "type": "trousers", "size": "small", "imageUrl": "https://www.hobbs.co.uk/images/products/0217-8604-9013L01-323/0217-8604-9013L01-323_02_1008_1080.jpg", "colour": "pink", "length": "long", "stock": 0 }, 80 | { "itemNumber": 1061, "type": "trousers", "size": "small", "imageUrl": "https://www.hobbs.co.uk/images/products/0217-8604-9013L01-323/0217-8604-9013L01-323_02_1008_1080.jpg", "colour": "pink", "length": "standard", "stock": 0 }, 81 | { "itemNumber": 1062, "type": "trousers", "size": "small", "imageUrl": "https://www.hobbs.co.uk/images/products/0217-8604-9013L01-323/0217-8604-9013L01-323_02_1008_1080.jpg", "colour": "pink", "length": "short", "stock": 0 }, 82 | { "itemNumber": 1063, "type": "trousers", "size": "medium", "imageUrl": "https://www.hobbs.co.uk/images/products/0217-8604-9013L01-323/0217-8604-9013L01-323_02_1008_1080.jpg", "colour": "pink", "length": "long", "stock": 0 }, 83 | { "itemNumber": 1064, "type": "trousers", "size": "medium", "imageUrl": "https://www.hobbs.co.uk/images/products/0217-8604-9013L01-323/0217-8604-9013L01-323_02_1008_1080.jpg", "colour": "pink", "length": "standard", "stock": 0 }, 84 | { "itemNumber": 1065, "type": "trousers", "size": "medium", "imageUrl": "https://www.hobbs.co.uk/images/products/0217-8604-9013L01-323/0217-8604-9013L01-323_02_1008_1080.jpg", "colour": "pink", "length": "short", "stock": 0 }, 85 | { "itemNumber": 1066, "type": "trousers", "size": "large", "imageUrl": "https://www.hobbs.co.uk/images/products/0217-8604-9013L01-323/0217-8604-9013L01-323_02_1008_1080.jpg", "colour": "pink", "length": "long", "stock": 0 }, 86 | { "itemNumber": 1067, "type": "trousers", "size": "large", "imageUrl": "https://www.hobbs.co.uk/images/products/0217-8604-9013L01-323/0217-8604-9013L01-323_02_1008_1080.jpg", "colour": "pink", "length": "standard", "stock": 0 }, 87 | { "itemNumber": 1068, "type": "trousers", "size": "large", "imageUrl": "https://www.hobbs.co.uk/images/products/0217-8604-9013L01-323/0217-8604-9013L01-323_02_1008_1080.jpg", "colour": "pink", "length": "short", "stock": 0 }, 88 | 89 | { "itemNumber": 1069, "type": "shirt", "size": "small", "imageUrl": "https://rukminim1.flixcart.com/image/832/832/shirt/p/b/u/bfshtpc501bpnk-being-fab-46-original-imaecvnxxgkg4sas.jpeg?q=70", "colour": "pink", "stock": 4 }, 90 | { "itemNumber": 1070, "type": "shirt", "size": "medium", "imageUrl": "https://rukminim1.flixcart.com/image/832/832/shirt/p/b/u/bfshtpc501bpnk-being-fab-46-original-imaecvnxxgkg4sas.jpeg?q=70", "colour": "pink", "stock": 6 }, 91 | { "itemNumber": 1071, "type": "shirt", "size": "large", "imageUrl": "https://rukminim1.flixcart.com/image/832/832/shirt/p/b/u/bfshtpc501bpnk-being-fab-46-original-imaecvnxxgkg4sas.jpeg?q=70", "colour": "pink", "stock": 5 }, 92 | 93 | { "itemNumber": 1072, "type": "jacket", "size": "small", "imageUrl": "https://www.collectif.co.uk/images/outlaw-plain-biker-jacket-p4253-150101_image.jpg", "colour": "pink", "stock": 1 }, 94 | { "itemNumber": 1073, "type": "jacket", "size": "medium", "imageUrl": "https://www.collectif.co.uk/images/outlaw-plain-biker-jacket-p4253-150101_image.jpg", "colour": "pink", "stock": 3 }, 95 | { "itemNumber": 1074, "type": "jacket", "size": "large", "imageUrl": "https://www.collectif.co.uk/images/outlaw-plain-biker-jacket-p4253-150101_image.jpg", "colour": "pink", "stock": 4 } 96 | ] 97 | } -------------------------------------------------------------------------------- /Chapter08/productFind/LexResponses.js: -------------------------------------------------------------------------------- 1 | module.exports = class Lex { 2 | 3 | elicitSlot({ sessionAttributes = {}, message, intentName, slotToElicit, slots, responseCard = null }) { 4 | return { 5 | sessionAttributes, 6 | dialogAction: { 7 | type: 'ElicitSlot', 8 | intentName, 9 | slots, 10 | slotToElicit, 11 | message: { contentType: 'PlainText', content: message }, 12 | responseCard 13 | }, 14 | }; 15 | } 16 | 17 | close({ message, sessionAttributes = {}, fulfillmentState = "Fulfilled", responseCard = null }) { 18 | return { 19 | sessionAttributes, 20 | dialogAction: { 21 | type: 'Close', 22 | fulfillmentState, 23 | message: { contentType: 'PlainText', content: message }, 24 | responseCard 25 | } 26 | } 27 | } 28 | 29 | elicitIntent({ message, sessionAttributes = {}, responseCard = null }) { 30 | return { 31 | sessionAttributes, 32 | dialogAction: { 33 | type: 'ElicitIntent', 34 | message: { contentType: 'PlainText', content: message }, 35 | responseCard 36 | }, 37 | }; 38 | } 39 | 40 | confirmIntent({ sessionAttributes = {}, intentName, slots, message, responseCard = null }) { 41 | return { 42 | sessionAttributes, 43 | dialogAction: { 44 | type: 'ConfirmIntent', 45 | intentName, 46 | slots, 47 | message: { contentType: 'PlainText', content: message }, 48 | responseCard 49 | }, 50 | }; 51 | } 52 | 53 | delegate({ sessionAttributes = {}, slots }) { 54 | return { 55 | sessionAttributes, 56 | dialogAction: { 57 | type: 'Delegate', 58 | slots 59 | }, 60 | }; 61 | } 62 | 63 | createCardFormat(cards) { 64 | return { 65 | version: 1, 66 | contentType: "application/vnd.amazonaws.card.generic", 67 | genericAttachments: cards.map(({ title, subTitle, imageUrl, linkUrl, buttons }) => { 68 | return { 69 | title, 70 | subTitle, 71 | imageUrl, 72 | attachmentLinkUrl: linkUrl, 73 | buttons: buttons.map(({ text, value }) => { 74 | return { 75 | text, 76 | value, 77 | } 78 | }) 79 | } 80 | }) 81 | } 82 | } 83 | } -------------------------------------------------------------------------------- /Chapter08/productFind/archive.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Hands-On-Chatbot-Development-with-Alexa-Skills-and-Amazon-Lex/a90bd11fcdb4c0c42b7d30094413e36106826d1f/Chapter08/productFind/archive.zip -------------------------------------------------------------------------------- /Chapter08/productFind/index.js: -------------------------------------------------------------------------------- 1 | const lex = require('./LexResponses'); 2 | const Lex = new lex(); 3 | const AWS = require('aws-sdk'); 4 | const s3 = new AWS.S3(); 5 | 6 | 7 | exports.handler = async (event) => { 8 | if (event.currentIntent && event.currentIntent.confirmationStatus) { 9 | let confirmationStatus = event.currentIntent.confirmationStatus; 10 | if (confirmationStatus == "Denied") { 11 | console.log('got denied status'); 12 | let message = `Thank you for shopping with us today. Have a nice day` 13 | return Lex.close({ message }) 14 | } 15 | if (confirmationStatus == 'Confirmed') { 16 | console.log('got confirmed status'); 17 | } 18 | } 19 | return handleProductFind(event); 20 | } 21 | 22 | const handleProductFind = event => { 23 | let { slots } = event.currentIntent; 24 | let { itemNumber, type, size, colour, length } = slots; 25 | 26 | if (itemNumber) return getItem(slots); 27 | // No item number so using normal product find 28 | if (!type) { 29 | let message = 'Are you looking for a shirt, jacket or trousers?'; 30 | let intentName = 'productFind'; 31 | let slotToElicit = 'type'; 32 | return Lex.elicitSlot({ message, intentName, slotToElicit, slots }) 33 | } 34 | if (!size) { 35 | let message = `What size of ${type} are you looking for?`; 36 | let intentName = 'productFind'; 37 | let slotToElicit = 'size'; 38 | return Lex.elicitSlot({ message, intentName, slotToElicit, slots }) 39 | } 40 | if (!colour) { 41 | let message = 'What colour would you like?'; 42 | let intentName = 'productFind'; 43 | let slotToElicit = 'colour'; 44 | return Lex.elicitSlot({ message, intentName, slotToElicit, slots }) 45 | } 46 | if (!length && type === 'trousers¡') { 47 | let message = 'Are you looking for short, standard or long trousers?'; 48 | let intentName = 'productFind'; 49 | let slotToElicit = 'length'; 50 | return Lex.elicitSlot({ message, intentName, slotToElicit, slots }) 51 | } 52 | 53 | return getItem(slots); 54 | } 55 | 56 | const getItem = async slots => { 57 | console.log('slots', slots); 58 | let { itemNumber, type, size, colour, length } = slots; 59 | let stock = await getStock(); 60 | let matching = stock.filter(item => 61 | itemNumber === item.itemNumber || 62 | type == item.type && 63 | size == item.size && 64 | colour == item.colour && 65 | (item.length == length || item.type != 'trousers')); 66 | if (matching.length !== 1) { 67 | let message = `Unfortunately we couldn't find the item you were looking for`; 68 | return Lex.close({ message }) 69 | } 70 | let item = matching[0]; 71 | if (item.stock < 1) { 72 | let message = `Unfortunately we don't have anything matching your request in stock. Would you like to search again?`; 73 | let intentName = 'productFind'; 74 | slots = { type: null, size: null, colour: null, length: null, itemNumber: null }; 75 | return Lex.confirmIntent({ intentName, slots, message }) 76 | } 77 | let message = `There are ${item.stock} ${item.colour} ${units(item.type, item.stock)} in stock. Would you like to add one to your basket?`; 78 | let responseCard = Lex.createCardFormat([{ 79 | title: `${size}, ${colour}${type === 'trousers' ? ', ' + length : ''}${type}`, 80 | subTitle: `${item.stock} in stock`, 81 | imageUrl: item.imageUrl, 82 | buttons: [{ text: 'Add to Cart', value: 'Yes' }, { text: 'Not Now', value: 'No' }] 83 | }]); 84 | let intentName = 'addToCart'; 85 | slots = { itemNumber: item.itemNumber }; 86 | return Lex.confirmIntent({ intentName, slots, message, responseCard }); 87 | } 88 | 89 | const units = (type, stock) => { 90 | if (type === 'trousers') { 91 | return `pair${stock === 1 ? 's': ''} of trousers` 92 | } 93 | return `${type}${stock === 1 ? 's': ''}`; 94 | } 95 | 96 | const getStock = () => { 97 | var params = { 98 | Bucket: 'shopping-stock', 99 | Key: `stock.json` 100 | }; 101 | 102 | return new Promise((resolve, reject) => { 103 | s3.getObject(params, function(err, data) { 104 | if (err) { // an error occurred 105 | reject(err) 106 | } else { // successful response 107 | resolve(JSON.parse(data.Body).stock) 108 | } 109 | }); 110 | }) 111 | } -------------------------------------------------------------------------------- /Chapter08/productFind/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "lockfileVersion": 1 3 | } 4 | -------------------------------------------------------------------------------- /Chapter08/productFind/tests: -------------------------------------------------------------------------------- 1 | no slots 2 | 3 | { 4 | "currentIntent": { 5 | "slots": { 6 | "type": null, 7 | "size": null, 8 | "colour": null, 9 | "length": null, 10 | "itemNumber": null 11 | } 12 | } 13 | } 14 | 15 | all slots and confirmed 16 | 17 | { 18 | "currentIntent": { 19 | "slots": { 20 | "type": "shirt", 21 | "size": "medium", 22 | "colour": "blue", 23 | "length": null, 24 | "itemNumber": null 25 | }, 26 | "confirmationStatus": "Confirmed" 27 | } 28 | } 29 | 30 | Denied 31 | 32 | { 33 | "currentIntent": { 34 | "slots": { 35 | "type": "shirt", 36 | "size": "medium", 37 | "colour": "blue", 38 | "length": null, 39 | "itemNumber": null 40 | }, 41 | "confirmationStatus": "Denied" 42 | } 43 | } 44 | 45 | ItemNumber slot 46 | 47 | { 48 | "currentIntent": { 49 | "slots": { 50 | "type": null, 51 | "size": null, 52 | "colour": null, 53 | "length": null, 54 | "itemNumber": 1015 55 | } 56 | } 57 | } 58 | 59 | 60 | -------------------------------------------------------------------------------- /Chapter08/weatherGods/archive.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Hands-On-Chatbot-Development-with-Alexa-Skills-and-Amazon-Lex/a90bd11fcdb4c0c42b7d30094413e36106826d1f/Chapter08/weatherGods/archive.zip -------------------------------------------------------------------------------- /Chapter08/weatherGods/index.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios'); 2 | const moment = require('moment'); 3 | const Alexa = require('ask-sdk'); 4 | 5 | const LaunchRequestHandler = { 6 | canHandle(handlerInput) { 7 | return handlerInput.requestEnvelope.request.type === 'LaunchRequest'; 8 | }, 9 | handle(handlerInput) { 10 | const speechText = 'You may ask the weather gods about the weather in your city or for a weather forecast'; 11 | 12 | return handlerInput.responseBuilder 13 | .speak(speechText) 14 | .reprompt(speechText) 15 | .getResponse(); 16 | } 17 | }; 18 | 19 | const ErrorHandler = { 20 | canHandle() { 21 | return true; 22 | }, 23 | handle(handlerInput, error) { 24 | console.log(`Error handled: ${error.message}`); 25 | 26 | return handlerInput.responseBuilder 27 | .speak(`Sorry, I can't understand the command. Please say again.`) 28 | .getResponse(); 29 | }, 30 | }; 31 | 32 | let jokes = [ 33 | `Where do snowmen keep their money? In a snow bank `, 34 | `As we waited for a bus in the frosty weather, the woman next to me mentioned that she makes a lot of mistakes when texting in the cold. I nodded knowingly. It’s the early signs of typothermia `, 35 | `Don’t knock the weather. If it didn’t change once in a while, nine tenths of the people couldn’t start a conversation` 36 | ]; 37 | 38 | const JokeHandler = { 39 | canHandle(handlerInput) { 40 | return handlerInput.requestEnvelope.request.type === 'IntentRequest' && 41 | handlerInput.requestEnvelope.request.intent.name === 'tellAJoke'; 42 | }, 43 | async handle(handlerInput) { 44 | let random = Math.floor(Math.random() * 3); 45 | let joke = jokes[random]; 46 | return handlerInput.responseBuilder 47 | .speak(joke) 48 | .withShouldEndSession(false) 49 | .getResponse(); 50 | } 51 | }; 52 | 53 | const GetWeatherHandler = { 54 | canHandle(handlerInput) { 55 | return handlerInput.requestEnvelope.request.type === 'IntentRequest' && 56 | handlerInput.requestEnvelope.request.intent.name === 'getWeather'; 57 | }, 58 | async handle(handlerInput) { 59 | console.log('start get weather'); 60 | const { slots } = handlerInput.requestEnvelope.request.intent; 61 | let { location, date } = slots; 62 | let sessionAttributes = handlerInput.attributesManager.getSessionAttributes(); 63 | console.log('sessionAttributes', sessionAttributes); 64 | location = location.value || sessionAttributes.location || null; 65 | date = date.value || sessionAttributes.date || null; 66 | sessionAttributes = { location, date }; 67 | 68 | if (!location) { 69 | console.log('get location'); 70 | let slotToElicit = 'location'; 71 | let speechOutput = 'Where do you want to know the weather for?'; 72 | return handlerInput.responseBuilder 73 | .speak(speechOutput) 74 | .addElicitSlotDirective(slotToElicit) 75 | .getResponse(); 76 | } 77 | if (!date) { 78 | date = Date.now() 79 | } 80 | 81 | let isToday = moment(date).isSame(Date.now(), 'day'); 82 | 83 | try { 84 | if (isToday) { 85 | console.log('isToday'); 86 | let [error, response] = await to(axios.get(`https://api.openweathermap.org/data/2.5/weather?q=${location},us&appid=${process.env.API_KEY}`)); 87 | let { data: weatherResponse } = response; 88 | if (error) { 89 | console.log('error getting weather', error.response); 90 | let errorSpeech = `We couldn't get the weather for ${location} but you can try again later`; 91 | return handlerInput.responseBuilder 92 | .speak(errorSpeech) 93 | .getResponse(); 94 | } 95 | let { weather, main: { temp, humidity } } = weatherResponse; 96 | console.log('got weather response', weatherResponse); 97 | let weatherString = formatWeatherString(weather); 98 | let formattedTemp = tempC(temp); 99 | // let formattedTemp = tempF(temp); 100 | let speechText = `The weather in ${location} has ${weatherString} with a temperature of ${formattedTemp} and a humidity of ${humidity} percent`; 101 | handlerInput.attributesManager.setSessionAttributes(sessionAttributes); 102 | return handlerInput.responseBuilder 103 | .speak(speechText) 104 | .withShouldEndSession(false) 105 | .getResponse(); 106 | } else { 107 | let { data: forecastResponse } = await axios.get(`https://api.openweathermap.org/data/2.5/forecast?q=${location},us&appid=${process.env.API_KEY}`); 108 | let { list } = forecastResponse; 109 | // reduce the data we keep 110 | let usefulForecast = list.map(weatherPeriod => { 111 | let { dt_txt, weather, main: { temp, humidity } } = weatherPeriod; 112 | return { dt_txt, weather, temp, humidity } 113 | }); 114 | // reduce to 9am and 6pm forecasts only 115 | let reducedForecast = usefulForecast.filter(weatherPeriod => { 116 | let time = weatherPeriod.dt_txt.slice(-8); 117 | return time === '09:00:00' || time === '18:00:00'; 118 | }); 119 | console.log('got forecaset and reduced it ', reducedForecast); 120 | // reduce to the day the user asked about 121 | let dayForecast = reducedForecast.filter(forecast => { 122 | return moment(date).isSame(forecast.dt_txt, 'day'); 123 | }); 124 | 125 | let weatherString = dayForecast.map(forecast => formatWeatherString(forecast.weather)); 126 | let formattedTemp = dayForecast.map(forecast => tempC(forecast.temp)); 127 | let humidity = dayForecast.map(forecast => forecast.humidity); 128 | let speechText = `The weather in ${location} ${date} will have ${weatherString[0]} with a temperature of ${formattedTemp[0]} and a humidity of ${humidity[0]} percent, whilst in the afternoon it will have ${weatherString[1]} with a temperature of ${formattedTemp[1]} and a humidity of ${humidity[1]} percent`; 129 | handlerInput.attributesManager.setSessionAttributes(sessionAttributes); 130 | return handlerInput.responseBuilder 131 | .speak(speechText) 132 | .withShouldEndSession(false) 133 | .getResponse(); 134 | } 135 | } catch (err) { 136 | console.log('err', err) 137 | return handlerInput.responseBuilder 138 | .speak(`My powers are weak and I couldn't get the weather right now.`) 139 | .getResponse(); 140 | } 141 | } 142 | } 143 | 144 | const SearchHandler = { 145 | canHandle(handlerInput) { 146 | return handlerInput.requestEnvelope.request.type === 'IntentRequest' && 147 | handlerInput.requestEnvelope.request.intent.name === 'searchIntent'; 148 | }, 149 | async handle(handlerInput) { 150 | const { slots } = handlerInput.requestEnvelope.request.intent; 151 | let { city, query } = slots; 152 | let cityValue = city.value; 153 | let queryValue = query.value; 154 | if (!cityValue) { 155 | let slotToElicit = 'city'; 156 | let speechOutput = `What city are you looking in?`; 157 | return handlerInput.responseBuilder 158 | .speak(speechOutput) 159 | .addElicitSlotDirective(slotToElicit) 160 | .getResponse(); 161 | } 162 | if (!queryValue) { 163 | let slotToElicit = 'query'; 164 | let speechOutput = `What are you looking for in ${cityValue}`; 165 | return handlerInput.responseBuilder 166 | .speak(speechOutput) 167 | .addElicitSlotDirective(slotToElicit) 168 | .getResponse(); 169 | } 170 | let completeURL = googleURL + [queryValue, 'in', cityValue].join('%20') + queryString + GOOGLE_API_KEY; 171 | let [err, res] = await to(axios.get(completeURL)); 172 | if (err || !res || !res.data) { 173 | let apology = `unfortunately I couldn't find that for you`; 174 | return handlerInput.responseBuilder 175 | .speak(apology) 176 | .getResponse(); 177 | } 178 | console.log('res', res); 179 | let data = res.data; 180 | let info = `There's ${data.candidates.length} ${query.value}${data.candidates.length === 1 ? "" : 's'} in ${city.value}. 181 | ${data.candidates.map(candidate => `the ${candidate.name}`)}`; 182 | return handlerInput.responseBuilder 183 | .speak(info) 184 | .withShouldEndSession(false) 185 | .getResponse(); 186 | } 187 | } 188 | 189 | const to = promise => promise.then(res => [null, res]).catch(err => [err, null]); 190 | 191 | const tempC = temp => Math.floor(temp - 273.15) + ' degrees Celsius '; 192 | 193 | const tempF = temp => Math.floor(9 / 5 * (temp - 273) + 32) + ' Fahrenheit'; 194 | 195 | const formatWeatherString = weather => { 196 | if (weather.length === 1) return weather[0].description; 197 | return weather.slice(0, -1).map(item => item.description).join(', ') + ' and ' + weather.slice(-1)[0].description; 198 | }; 199 | 200 | 201 | exports.handler = Alexa.SkillBuilders.custom() 202 | .addRequestHandlers( 203 | LaunchRequestHandler, 204 | GetWeatherHandler, 205 | JokeHandler, 206 | SearchHandler 207 | ) 208 | .addErrorHandlers(ErrorHandler) 209 | .lambda(); -------------------------------------------------------------------------------- /Chapter08/weatherGods/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "weathergods", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "alexa-sdk": { 8 | "version": "1.0.25", 9 | "resolved": "https://registry.npmjs.org/alexa-sdk/-/alexa-sdk-1.0.25.tgz", 10 | "integrity": "sha512-+FVFNi+mxBZm2HL+oi5u4JTNjQ2uDs4Tp9eqcWIxL3AAD+AU4a6gWpu6LEjxIVCqaI1Ro/RyDm3mnJZA9g6G8w==", 11 | "requires": { 12 | "aws-sdk": "2.282.1", 13 | "i18next": "3.5.2", 14 | "i18next-sprintf-postprocessor": "0.2.2" 15 | } 16 | }, 17 | "aws-sdk": { 18 | "version": "2.282.1", 19 | "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.282.1.tgz", 20 | "integrity": "sha512-2/QFnzANnU/Fy4hdCz93bq8ERG2gANfGytt4cZGEPC0V6ePx22JqYGZMYUmBEGsWDCKbKc8l8QZDDer4r/0QBw==", 21 | "requires": { 22 | "buffer": "4.9.1", 23 | "events": "1.1.1", 24 | "ieee754": "1.1.8", 25 | "jmespath": "0.15.0", 26 | "querystring": "0.2.0", 27 | "sax": "1.2.1", 28 | "url": "0.10.3", 29 | "uuid": "3.1.0", 30 | "xml2js": "0.4.19" 31 | } 32 | }, 33 | "axios": { 34 | "version": "0.18.0", 35 | "resolved": "https://registry.npmjs.org/axios/-/axios-0.18.0.tgz", 36 | "integrity": "sha1-MtU+SFHv3AoRmTts0AB4nXDAUQI=", 37 | "requires": { 38 | "follow-redirects": "1.5.1", 39 | "is-buffer": "1.1.6" 40 | } 41 | }, 42 | "base64-js": { 43 | "version": "1.3.0", 44 | "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.0.tgz", 45 | "integrity": "sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw==" 46 | }, 47 | "buffer": { 48 | "version": "4.9.1", 49 | "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", 50 | "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", 51 | "requires": { 52 | "base64-js": "1.3.0", 53 | "ieee754": "1.1.8", 54 | "isarray": "1.0.0" 55 | } 56 | }, 57 | "debug": { 58 | "version": "3.1.0", 59 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", 60 | "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", 61 | "requires": { 62 | "ms": "2.0.0" 63 | } 64 | }, 65 | "events": { 66 | "version": "1.1.1", 67 | "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", 68 | "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=" 69 | }, 70 | "follow-redirects": { 71 | "version": "1.5.1", 72 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.1.tgz", 73 | "integrity": "sha512-v9GI1hpaqq1ZZR6pBD1+kI7O24PhDvNGNodjS3MdcEqyrahCp8zbtpv+2B/krUnSmUH80lbAS7MrdeK5IylgKg==", 74 | "requires": { 75 | "debug": "3.1.0" 76 | } 77 | }, 78 | "i18next": { 79 | "version": "3.5.2", 80 | "resolved": "https://registry.npmjs.org/i18next/-/i18next-3.5.2.tgz", 81 | "integrity": "sha1-kwOQ1cMYzqpIWLUt0OQOayA/n0E=" 82 | }, 83 | "i18next-sprintf-postprocessor": { 84 | "version": "0.2.2", 85 | "resolved": "https://registry.npmjs.org/i18next-sprintf-postprocessor/-/i18next-sprintf-postprocessor-0.2.2.tgz", 86 | "integrity": "sha1-LkCfEENXk4Jpi2otpwzapVHWfqQ=" 87 | }, 88 | "ieee754": { 89 | "version": "1.1.8", 90 | "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.8.tgz", 91 | "integrity": "sha1-vjPUCsEO8ZJnAfbwii2G+/0a0+Q=" 92 | }, 93 | "is-buffer": { 94 | "version": "1.1.6", 95 | "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", 96 | "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" 97 | }, 98 | "isarray": { 99 | "version": "1.0.0", 100 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 101 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" 102 | }, 103 | "jmespath": { 104 | "version": "0.15.0", 105 | "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz", 106 | "integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc=" 107 | }, 108 | "moment": { 109 | "version": "2.22.2", 110 | "resolved": "https://registry.npmjs.org/moment/-/moment-2.22.2.tgz", 111 | "integrity": "sha1-PCV/mDn8DpP/UxSWMiOeuQeD/2Y=" 112 | }, 113 | "ms": { 114 | "version": "2.0.0", 115 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 116 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 117 | }, 118 | "punycode": { 119 | "version": "1.3.2", 120 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", 121 | "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" 122 | }, 123 | "querystring": { 124 | "version": "0.2.0", 125 | "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", 126 | "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" 127 | }, 128 | "sax": { 129 | "version": "1.2.1", 130 | "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", 131 | "integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o=" 132 | }, 133 | "url": { 134 | "version": "0.10.3", 135 | "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", 136 | "integrity": "sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=", 137 | "requires": { 138 | "punycode": "1.3.2", 139 | "querystring": "0.2.0" 140 | } 141 | }, 142 | "uuid": { 143 | "version": "3.1.0", 144 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz", 145 | "integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g==" 146 | }, 147 | "xml2js": { 148 | "version": "0.4.19", 149 | "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", 150 | "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", 151 | "requires": { 152 | "sax": "1.2.1", 153 | "xmlbuilder": "9.0.7" 154 | } 155 | }, 156 | "xmlbuilder": { 157 | "version": "9.0.7", 158 | "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", 159 | "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=" 160 | } 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /Chapter08/weatherGods/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "weathergods", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "alexa-sdk": "^1.0.25", 13 | "axios": "^0.18.0", 14 | "moment": "^2.22.2" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Packt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ## $5 Tech Unlocked 2021! 5 | [Buy and download this Book for only $5 on PacktPub.com](https://www.packtpub.com/product/hands-on-chatbot-development-with-alexa-skills-and-amazon-lex/9781788993487) 6 | ----- 7 | *If you have read this book, please leave a review on [Amazon.com](https://www.amazon.com/gp/product/1788993489). Potential readers can then use your unbiased opinion to help them make purchase decisions. Thank you. The $5 campaign runs from __December 15th 2020__ to __January 13th 2021.__* 8 | 9 | # Hands-On Chatbot Development with Alexa Skills and Amazon Lex 10 | 11 | Hands-On Chatbot Development with Alexa Skills and Amazon Lex 12 | 13 | This is the code repository for [Hands-On Chatbot Development with Alexa Skills and Amazon Lex](https://www.packtpub.com/web-development/hands-chatbot-development-alexa-skills-and-amazon-lex?utm_source=github&utm_medium=repository&utm_campaign=9781788993487), published by Packt. 14 | 15 | **Create custom conversational and voice interfaces for your Amazon Echo devices and web platforms** 16 | 17 | ## What is this book about? 18 | This book will help you to discover important AWS services such as S3 and DyanmoDB. Gain practical experience building end-to-end application workflows using NodeJS and AWS Lambda for your Alexa Skills Kit. You will be able to build conversational interfaces using voice or text and deploy them to platforms like Alexa, Facebook Messenger and Slack. 19 | 20 | This book covers the following exciting features: 21 | * Create a development environment using Alexa Skills Kit, AWS CLI, and Node.js 22 | * Build Alexa Skills and Lex chatbots from scratch 23 | * Gain access to third-party APIs from your Alexa Skills and Lex chatbots 24 | * Use AWS services such as Amazon S3 and DynamoDB to enhance the abilities of your Alexa Skills and Amazon Lex chatbots 25 | * Publish a Lex chatbot to Facebook Messenger, Twilio SMS, and Slack 26 | * Create a custom website for your Lex chatbots 27 | * Develop your own skills for Alexa-enabled devices such as the Echo 28 | 29 | If you feel this book is for you, get your [copy](https://www.amazon.com/dp/1788993489) today! 30 | 31 | https://www.packtpub.com/ 33 | 34 | 35 | ## Instructions and Navigations 36 | All of the code is organized into folders. For example, Chapter02. 37 | 38 | The code will look like the following: 39 | ``` 40 | return new Promise((resolve, reject) => { 41 | s3.getObject(params, function(err, data) { 42 | if (err) { // an error occurred 43 | reject(handleS3Error(err)); 44 | } else { // successful response 45 | console.log(data); 46 | resolve(handleS3Data(data, intentName)); 47 | } 48 | }); 49 | }) 50 | ``` 51 | 52 | **Following is what you need for this book:** 53 | This book is for anyone who wants to be able to build Alexa Skills or Lex chatbots. Whether you want to build them for personal projects or as part of your job, this book will give you all the tools you need. You'll be able to take an idea, build the conversation flow diagrams, test them with user stories, and then build your Alexa Skill or Lex chatbot. 54 | 55 | With the following software and hardware list you can run all code files present in the book (Chapter 1-11). 56 | 57 | ### Software and Hardware List 58 | 59 | | Chapter | Software required | OS required | 60 | | -------- | ------------------------------------ | ------------------------------------ | 61 | | 2-8 | Node 8.10 or later |No specific requirement | 62 | 63 | 64 | 65 | We also provide a PDF file that has color images of the screenshots/diagrams used in this book. [Click here to download it](https://www.packtpub.com/sites/default/files/downloads/9781788993487_ColorImages.pdf). 66 | 67 | ### Related products 68 | * Hands-On Chatbots and Conversational UI Development [[Packt]](https://www.packtpub.com/application-development/hands-chatbots-and-conversational-ui-development?utm_source=github&utm_medium=repository&utm_campaign=9781788294669) [[Amazon]](https://www.amazon.com/dp/1788294661) 69 | 70 | * Voice User Interface Projects [[Packt]](https://www.packtpub.com/web-development/voice-user-interface-projects?utm_source=github&utm_medium=repository&utm_campaign=9781788473354) [[Amazon]](https://www.amazon.com/dp/1788473353) 71 | 72 | 73 | ## Get to Know the Author 74 | **Sam Williams** 75 | qualified with an aerospace engineering master's degree, then became a self-taught software developer while holding down his first job. While traveling, he started to write articles about the tech he was learning about and accrued an audience of readers on Medium and freeCodeCamp. 76 | Currently, Sam works as a lead chatbot developer for the SmartAgent team at MissionLabs, building custom systems for large retailers. His role ensures that he is working with the newest chatbot technologies and is constantly pushing their capabilities. 77 | 78 | 79 | 80 | 81 | 82 | 83 | ### Suggestions and Feedback 84 | [Click here](https://docs.google.com/forms/d/e/1FAIpQLSdy7dATC6QmEL81FIUuymZ0Wy9vH1jHkvpY57OiMeKGqib_Ow/viewform) if you have any feedback or suggestions. 85 | 86 | 87 | ### Download a free PDF 88 | 89 | If you have already purchased a print or Kindle version of this book, you can get a DRM-free PDF version at no cost.
Simply click on the link to claim your free PDF.
90 |

https://packt.link/free-ebook/9781788993487

-------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | if [ "$#" -ne 1 ]; then 2 | echo "Usage : ./build.sh lambdaName"; 3 | exit 1; 4 | fi 5 | 6 | lambda=${1%/}; // # Removes trailing slashes 7 | echo "Deploying $lambda"; 8 | cd $lambda; 9 | if [ $? -eq 0 ]; then 10 | echo "...." 11 | else 12 | echo "Couldn't cd to directory $lambda. You may have mis-spelled the lambda/directory name"; 13 | exit 1 14 | fi 15 | 16 | echo "nmp installing..."; 17 | npm install 18 | if [ $? -eq 0 ]; then 19 | echo "done"; 20 | else 21 | echo "npm install failed"; 22 | exit 1; 23 | fi 24 | 25 | echo "Checking that aws-cli is installed" 26 | which aws 27 | if [ $? -eq 0 ]; then 28 | echo "aws-cli is installed, continuing..." 29 | else 30 | echo "You need aws-cli to deploy this lambda. Google 'aws-cli install'" 31 | exit 1 32 | fi 33 | 34 | echo "removing old zip" 35 | rm archive.zip; 36 | 37 | echo "creating a new zip file" 38 | zip archive.zip * -r -x .git/\* \*.sh tests/\* node_modules/aws-sdk/\* \*.zip 39 | 40 | echo "Uploading $lambda"; 41 | aws lambda create-function --function-name $lambda --role arn:aws:iam::095363550084:role/lambdaBasic --runtime nodejs8.10 --handler index.handler --zip-file fileb://archive.zip --publish --profile samwcoding 42 | 43 | if [ $? -eq 0 ]; then 44 | echo "!! Create successful !!" 45 | exit 1; 46 | fi 47 | 48 | aws lambda update-function-code --function-name $lambda --zip-file fileb://archive.zip --publish --profile samwcoding 49 | 50 | if [ $? -eq 0 ]; then 51 | echo "!! Update successful !!" 52 | else 53 | echo "Upload failed" 54 | echo "If the error was a 400, check that there are no slashes in your lambda name" 55 | echo "Lambda name = $lambda" 56 | exit 1; 57 | fi --------------------------------------------------------------------------------