Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Alexa CanFulfillIntentRequest #378

Open
wants to merge 36 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
3ea1bbf
Update index.js
orlylev Sep 18, 2018
6865ed2
Update index.js
orlylev Sep 18, 2018
1ddcd44
Update index.js
orlylev Sep 23, 2018
5aebc33
add documentation
orlylev Oct 18, 2018
107f00f
Update README.md
orlylev Oct 18, 2018
ed81661
Update README.md
orlylev Oct 18, 2018
edcc431
Update README.md
orlylev Oct 18, 2018
13a462a
Update README.md
orlylev Oct 18, 2018
8132415
Update README.md
orlylev Oct 18, 2018
9417632
Update README.md
orlylev Oct 18, 2018
c88e2dd
add missinf semicolon
orlylev Oct 21, 2018
901f077
add missing whitespace
orlylev Oct 21, 2018
29da179
Update CHANGELOG.md
orlylev Oct 21, 2018
a5ba4d6
fix travis issues
orlylev Oct 21, 2018
62bbdff
move contribution line to the end
orlylev Oct 21, 2018
de85028
Add again after force rebase
orlylev Oct 21, 2018
77d81d6
Commit again after force rebase
orlylev Oct 21, 2018
7051bec
Commit again after force rebase
orlylev Oct 21, 2018
3e53677
add test file
orlylev Oct 21, 2018
9d39499
add unut testing CanFulfillIntent
orlylev Oct 21, 2018
e69adf7
Update test/test_alexa_app_fulfill_intent_request.js
orlylev Oct 21, 2018
2113610
Update CHANGELOG.md
orlylev Oct 21, 2018
5ea6a01
Update test/test_alexa_app_fulfill_intent_request.js
orlylev Oct 21, 2018
345c768
fix getCanFulfillIntent text
orlylev Oct 22, 2018
efb9773
add getCanFulfillIntent slot test
orlylev Oct 22, 2018
efaf531
add slot test
orlylev Oct 22, 2018
35b9a69
Updated PR number
orlylev Oct 23, 2018
f17440b
add tests
orlylev Oct 23, 2018
46f3320
Merge branch 'master' into master
orlylev Oct 23, 2018
6afa04e
add test when no can fulfill intent in request
orlylev Oct 23, 2018
7b432ac
Merge branch 'master' of https://github.com/orlylev/alexa-app
orlylev Oct 23, 2018
4131bbf
Update index.js
orlylev Oct 23, 2018
b47731f
add not valid requests tests
orlylev Oct 23, 2018
69b018f
split canFulfillIntent vs canFulfillIntentValue
orlylev Oct 24, 2018
6e5accf
add negative tests for no intent
orlylev Oct 24, 2018
bb256af
Update README.md
orlylev Oct 29, 2018
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
* [#364](https://github.com/alexa-js/alexa-app/pull/364): Fix reprompt() to concatenate multiple SSML prompts - [@andrewjhunt](https://github.com/andrewjhunt).
* [#371](https://github.com/alexa-js/alexa-app/pull/371): Call response.prepare() after post() - [@fremail](https://github.com/fremail).
* [#373](https://github.com/alexa-js/alexa-app/pull/373): Keep multiple newlines in card output - [@kielni](https://github.com/kielni) and [@fremail](https://github.com/fremail).
* [#378](https://github.com/alexa-js/alexa-app/pull/375): Implement CanFulfillIntent requests - [@orlylev](https://github.com/orlylev).
* Your contribution here.

### 4.2.2 (April 7, 2018)
Expand Down
31 changes: 30 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
* [Display.ElementSelected](#display-element-selected)
* [AudioPlayer Event Request](#audioplayer-event-request)
* [PlaybackController Event Request](#playbackcontroller-event-request)
* [CanFulfillIntent Request](#canfulfillintent)
* [Other Event Request](#other-event-request)
* [Execute Code On Every Request](#execute-code-on-every-request)
* [pre()](#pre)
Expand Down Expand Up @@ -468,7 +469,6 @@ app.displayElementSelected(function(request, response) {
handleRequestForTouchEvent(request.selectedElementToken)
})
```

### SessionEndRequest

```javascript
Expand Down Expand Up @@ -555,6 +555,35 @@ app.playbackController('NextCommandIssued', (request, response) => {

Note that some device interactions don't always produce PlaybackController events. See the [PlaybackController Interface Introduction](https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/docs/custom-playbackcontroller-interface-reference#introduction) for more details.

### CanFulfillIntent

Define the handler for canFulfillIntent used for skill discovery. For instance the [Implementation instructions](https://developer.amazon.com/docs/custom-skills/implement-canfulfillintentrequest-for-name-free-interaction.html).

See detailed explanation on the Intent and Slot [logic](https://developer.amazon.com/docs/custom-skills/understand-name-free-interaction-for-custom-skills.html).


```javascript
app.canFulfillHandler(function(request, response) {
// The request object will return CanFulfillIntent object
//by default the response will include "No" for the intent and slots
let canFulfillIntent = request.getCanFulfillIntent();
lazerwalker marked this conversation as resolved.
Show resolved Hide resolved

//add your logic to determine if you can fulfill the intent and each slot
if(canFulfillIntent.name == 'HowTo'
|| canFulfillIntent.name == 'Information' || canFulfillIntent.name == 'Instructions')
{
//we response YES for these intents that we can fulfill
response.canFulfill("YES");
}
else if(canFulfillIntent.name == 'GetPrice'){
//In this information we check if we can answer GetPrice intent with the slots values and decide to answer YES

//Do the logic ...
response.canFulfillSlot("MyProduct","YES","YES");
}
});
```

### Other Event Request

Handle any new requests that don't have an explicit handler type available (such as new or pre-release features) using the general `on()` and passing the event type.
Expand Down
101 changes: 94 additions & 7 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,65 @@ alexa.response = function(session) {
this.sessionObject.clear(key);
return this;
};

this.canFulfillIntent= function(slots){
this.canFulfillIntentValue = new alexa.canFulfillIntent(self.response.response, slots);
return this;
};

this.canFulfill = function(canFulfill){
if(!this.canFulfillIntentValue){
this.canFulfillIntentValue = new alexa.canFulfillIntent(self.response.response);
}
this.canFulfillIntentValue.canFulfill(canFulfill);
return this;
};

this.canFulfillSlot = function(slotName,canUnderstand, canFulfill){
if(!this.canFulfillIntentValue){
this.canFulfillIntentValue = new alexa.canFulfillIntent(self.response.response);
}
this.canFulfillIntentValue.canFulfillSlot(slotName,canUnderstand, canFulfill);
return this;
};
};

alexa.canFulfillIntent = function (response, slots) {
//load alexa slots information from CanFulfillIntentRequest
//create default response
this.canFulfillIntent = {
"canFulfill": "NO",
"slots": {}
};

response.canFulfillIntent = this.canFulfillIntent;
if (slots) {
for (let slotName in slots) {
let slotValue = {
"canUnderstand": "NO",
"canFulfill": "NO"
};
this.canFulfillIntent.slots[slotName] = slotValue;
}
}

this.canFulfill = function(canFulfill){
this.canFulfillIntent.canFulfill = canFulfill;
};

this.canFulfillSlot = function(slotName,canUnderstand, canFulfill){
if(this.canFulfillIntent.slots[slotName]){
let canFulfillSlot = this.canFulfillIntent.slots[slotName];
canFulfillSlot.canUnderstand = canUnderstand;
canFulfillSlot.canFulfill = canFulfill;
}
else{
this.canFulfillIntent.slots[slotName] = {
"canUnderstand": canUnderstand,
"canFulfill": canFulfill
};
}
};
};

alexa.directives = function(directives) {
Expand All @@ -196,11 +255,15 @@ alexa.directives = function(directives) {
this.details.push(directive);
};



this.clear = function() {
this.details.length = 0;
};
};



alexa.request = function(json) {
this.data = json;
this.slots = {};
Expand Down Expand Up @@ -251,7 +314,9 @@ alexa.request = function(json) {
this.context = null;

if (this.data.context) {
this.userId = this.data.context.System.user.userId;
if(this.data.context.System && this.data.context.System.user){
this.userId = this.data.context.System.user.userId;
}
this.applicationId = this.data.context.System.application.applicationId;
this.context = this.data.context;
}
Expand Down Expand Up @@ -283,6 +348,12 @@ alexa.request = function(json) {
this.session = function(key) {
return this.getSession().get(key);
};

this.getCanFulfillIntent = function () {
if (this.data && this.data.request && this.data.request.intent) {
return this.data.request.intent;
}
};
};

alexa.dialog = function(dialogState) {
Expand Down Expand Up @@ -387,9 +458,13 @@ alexa.session = function(session) {
// load the alexa session information into details
this.details = session;
// @deprecated
this.details.userId = this.details.user.userId || null;
// @deprecated
this.details.accessToken = this.details.user.accessToken || null;
this.details.userId = null;
this.details.accessToken = null;
if(this.details.user){
this.details.userId = this.details.user.userId || null;
this.details.accessToken = this.details.user.accessToken || null;
}


// persist all the session attributes across requests
// the Alexa API doesn't think session variables should persist for the entire
Expand Down Expand Up @@ -527,6 +602,10 @@ alexa.app = function(name) {
this.sessionEnded = function(func) {
self.sessionEndedFunc = func;
};
this.canFulfillIntentFunc = null;
this.canFulfillIntent = function(func) {
self.canFulfillIntentFunc = func;
};
this.request = function(request_json) {
var request = new alexa.request(request_json);
var response = new alexa.response(request.getSession());
Expand All @@ -543,7 +622,6 @@ alexa.app = function(name) {
postPromise = Promise.resolve(self.post(request, response, requestType, exception));
}
return postPromise.then(function() {
response.prepare();
if (!response.resolved) {
response.resolved = true;
}
Expand All @@ -558,7 +636,6 @@ alexa.app = function(name) {
postPromise = Promise.resolve(self.post(request, response, requestType, exception));
}
return postPromise.then(function() {
response.prepare();
if (!response.resolved) {
response.resolved = true;
throw msg;
Expand Down Expand Up @@ -618,7 +695,17 @@ alexa.app = function(name) {
} else {
throw "NO_DISPLAY_ELEMENT_SELECTED_FUNCTION";
}
} else if (typeof self.requestHandlers[requestType] === "function") {
} else if ("CanFulfillIntentRequest" === requestType) {
if (typeof self.canFulfillIntentFunc === "function") {
let reqIntent = request.getCanFulfillIntent();
if(reqIntent){
response.canFulfillIntent(reqIntent.slots);
}
return Promise.resolve(self.canFulfillIntentFunc(request, response));
} else {
throw "NO_CAN_FULFILL_FUNCTION";
}
}else if (typeof self.requestHandlers[requestType] === "function") {
return Promise.resolve(self.requestHandlers[requestType](request, response, request_json));
} else {
throw "INVALID_REQUEST_TYPE";
Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
"babel-preset-es2015": "^6.24.1",
"chai": "^3.4.1",
"chai-as-promised": "^5.3.0",
"chai-string": "1.4.0",
"chai-string": "^1.3.0",
"coveralls": "^2.11.9",
"danger": "0.6.10",
"ejs": "^2.5.5",
Expand Down
37 changes: 37 additions & 0 deletions test/fixtures/can_fulfill_intent_request_no_intent.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"version": "1.0",
"session": {
"new": false,
"sessionId": "amzn1.echo-api.session.abeee1a7-aee0-41e6-8192-e6faaed9f5ef",
"application": {
"applicationId": "amzn1.echo-sdk-ams.app.000000-d0ed-0000-ad00-000000d00ebe"
},
"attributes": {},
"user": {
"userId": "amzn1.account.AM3B227HF3FAM1B261HK7FFM3A2"
}
},
"context": {
"System": {
"application": {
"applicationId": "amzn1.echo-sdk-ams.app.000000-d0ed-0000-ad00-000000d00ebe"
},
"user": {
"userId": "amzn1.account.AM3B227HF3FAM1B261HK7FFM3A2",
"accessToken": "ae312e92d87d7b4e7648b19a08f2966a831a59de"
},
"device": {
"deviceId": "amzn1.ask.device.123456789",
"supportedInterfaces": {}
},
"apiEndpoint": "https://api.amazonalexa.com",
"apiAccessToken": "123456789"
}
},
"request": {
"type": "CanFulfillIntentRequest",
"requestId": "amzn1.echo-api.request.e510e005-c518-44c8-930d-746c84e787da",
"locale": "en-US",
"timestamp": "2018-10-18T09:16:28Z"
}
}
46 changes: 46 additions & 0 deletions test/fixtures/can_fulfill_intent_request_play_sound.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
{
"version": "1.0",
"session": {
"new": false,
"sessionId": "amzn1.echo-api.session.abeee1a7-aee0-41e6-8192-e6faaed9f5ef",
"application": {
"applicationId": "amzn1.echo-sdk-ams.app.000000-d0ed-0000-ad00-000000d00ebe"
},
"attributes": {},
"user": {
"userId": "amzn1.account.AM3B227HF3FAM1B261HK7FFM3A2"
}
},
"context": {
"System": {
"application": {
"applicationId": "amzn1.echo-sdk-ams.app.000000-d0ed-0000-ad00-000000d00ebe"
},
"user": {
"userId": "amzn1.account.AM3B227HF3FAM1B261HK7FFM3A2",
"accessToken": "ae312e92d87d7b4e7648b19a08f2966a831a59de"
},
"device": {
"deviceId": "amzn1.ask.device.123456789",
"supportedInterfaces": {}
},
"apiEndpoint": "https://api.amazonalexa.com",
"apiAccessToken": "123456789"
}
},
"request": {
"type": "CanFulfillIntentRequest",
"requestId": "amzn1.echo-api.request.e510e005-c518-44c8-930d-746c84e787da",
"intent": {
"name": "PlaySound",
"slots": {
"Sound": {
"name": "Sound",
"value": "crickets"
}
}
},
"locale": "en-US",
"timestamp": "2018-10-18T09:16:28Z"
}
}