├── 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 |
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 |
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
--------------------------------------------------------------------------------