SickBeard Alexa skill 1.0
This commit is contained in:
commit
39b8d15540
12
.env
Normal file
12
.env
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
AWS_ENVIRONMENT=
|
||||||
|
AWS_SESSION_TOKEN=
|
||||||
|
AWS_REGION=us-east-1
|
||||||
|
AWS_FUNCTION_NAME=sickbeard
|
||||||
|
AWS_HANDLER=index.handler
|
||||||
|
AWS_MODE=event
|
||||||
|
AWS_MEMORY_SIZE=128
|
||||||
|
AWS_TIMEOUT=10
|
||||||
|
AWS_DESCRIPTION=
|
||||||
|
AWS_RUNTIME=nodejs
|
||||||
|
SB_URL=http://url-to-couch-potato-server
|
||||||
|
SB_API_KEY=apiKey
|
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
deploy.env
|
||||||
|
lambda.zip
|
||||||
|
node_modules/
|
55
README.md
Normal file
55
README.md
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
# Couch Potato Alexa Skill
|
||||||
|
|
||||||
|
This is a skill built for Amazon's Alexa service that tells you about your Couch Potato queue. It
|
||||||
|
allows you to ask Alexa the following:
|
||||||
|
|
||||||
|
> Alexa, ask Couch Potato to add The Godfather
|
||||||
|
|
||||||
|
> Alexa, ask Couch Potato if The Dark Knight is on the list
|
||||||
|
|
||||||
|
If you're just getting started developing skills for Alexa, I'd recommend reading [Getting Started
|
||||||
|
with the Alexa Skills
|
||||||
|
Kit](https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/getting-started-guide) and
|
||||||
|
[Developing an Alexa Skill as a Lambda
|
||||||
|
Function](https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/docs/developing-an-alexa-skill-as-a-lambda-function) to get familiar with the process.
|
||||||
|
|
||||||
|
## Configuring The Skill
|
||||||
|
|
||||||
|
To configure the skill, open up the `.env` file and fill in the correct values for `CP_URL`, which
|
||||||
|
should point to your Couch Potato server, and `CP_API_KEY` which should have your server's API key.
|
||||||
|
|
||||||
|
## Testing The Skill Locally
|
||||||
|
|
||||||
|
You can use [node-lambda](https://github.com/motdotla/node-lambda) to test this skill locally. In
|
||||||
|
the `test_events` directory are several event files you can use for testing, and they should map
|
||||||
|
pretty well to each Intent. To test an intent, simply copy the contents of one of the json files in
|
||||||
|
that directory and overwrite the contents of `event.json`. Then run `node-lambda run` from the
|
||||||
|
command line.
|
||||||
|
|
||||||
|
## Setting up the Skill
|
||||||
|
|
||||||
|
To set up the skill, head on over to [Alexa skills kit
|
||||||
|
development console](https://developer.amazon.com/edw/home.html) and add a new skill. Fill in the
|
||||||
|
basic skill information however you choose. For Endpoint, you'll need to fill in your Lambda ARN
|
||||||
|
which you'll get in the next step. Next, head on over to Interaction Model. In the Intent
|
||||||
|
Schema field, copy and paste the contents of the `interaction_model/intent_schema.json` file. Then
|
||||||
|
in the Sample Utterances field, copy and paste the contents of
|
||||||
|
`interaction_model/sample_utterances.txt`.
|
||||||
|
|
||||||
|
## Hosting the Skill
|
||||||
|
|
||||||
|
The skill is built to be easily hosted on Amazon's [AWS
|
||||||
|
Lambda service](https://aws.amazon.com/lambda/). Create your Lambda function (using the
|
||||||
|
alexa-skills-kit-color-expert blueprint) and make sure you choose Node.js as the runtime. After
|
||||||
|
you've created your Lambda function, look at the top right of the page to get your Lambda ARN
|
||||||
|
number and put that in the Alexa Skill Information Endpoint field.
|
||||||
|
|
||||||
|
To deploy to Lambda, first makes sure you do an `npm install` at the root of the project.
|
||||||
|
Once all the dependencies are installed, run `npm run bundle`, which will create a lambda.zip file.
|
||||||
|
You can then upload that zip file to Lambda for use in your function and skill.
|
||||||
|
|
||||||
|
You can also use [node-lambda](https://github.com/motdotla/node-lambda) to deploy to your Lambda
|
||||||
|
function directly from the command line. Simply add a deploy.env file with your environment
|
||||||
|
configuration (and double check the supplied .env file in this repository) and then run
|
||||||
|
`node-lambda deploy`. Please visit the [node-lambda](https://github.com/motdotla/node-lambda)
|
||||||
|
project page for more information on deploying from the command line.
|
27
event.json
Normal file
27
event.json
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"session": {
|
||||||
|
"sessionId": "SessionId.1234",
|
||||||
|
"application": {
|
||||||
|
"applicationId": "amzn1.echo-sdk-ams.app.1234"
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"userId": "amzn1.echo-sdk-account.1234"
|
||||||
|
},
|
||||||
|
"new": true
|
||||||
|
},
|
||||||
|
"request": {
|
||||||
|
"type": "IntentRequest",
|
||||||
|
"requestId": "EdwRequestId.1234",
|
||||||
|
"timestamp": "2016-01-01T00:00:00Z",
|
||||||
|
"intent": {
|
||||||
|
"name": "AddShow",
|
||||||
|
"slots": {
|
||||||
|
"showName": {
|
||||||
|
"name": "showName",
|
||||||
|
"value": "silicon valley"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"version": "1.0"
|
||||||
|
}
|
4
index.js
Normal file
4
index.js
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
var app = require('./lib');
|
||||||
|
exports.handler = app.lambda();
|
31
interaction_model/intent_schema.json
Normal file
31
interaction_model/intent_schema.json
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"intents": [
|
||||||
|
{
|
||||||
|
"intent": "AddShow",
|
||||||
|
"slots": [
|
||||||
|
{
|
||||||
|
"name": "showName",
|
||||||
|
"type": "AMAZON.LITERAL"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"intent": "FindShow",
|
||||||
|
"slots": [
|
||||||
|
{
|
||||||
|
"name": "showName",
|
||||||
|
"type": "AMAZON.LITERAL"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"intent": "AMAZON.YesIntent"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"intent": "AMAZON.NoIntent"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"intent": "AMAZON.CancelIntent"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
34
interaction_model/sample_utterances.txt
Normal file
34
interaction_model/sample_utterances.txt
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
AddShow add {How I Met Your Mother|showName}
|
||||||
|
AddShow add {Jessica Jones|showName}
|
||||||
|
AddShow add {Seinfeld|showName}
|
||||||
|
AddShow add {Better Call Saul|showName}
|
||||||
|
AddShow add {Parks and Recreation|showName}
|
||||||
|
AddShow add {Silicon Valley|showName}
|
||||||
|
|
||||||
|
FindShow is {How I Met Your Mother|showName} on the list
|
||||||
|
FindShow is {Jessica Jones|showName} on the list
|
||||||
|
FindShow is {Seinfeld|showName} on the list
|
||||||
|
FindShow is {Better Call Saul|showName} on the list
|
||||||
|
FindShow is {Parks and Recreation|showName} on the list
|
||||||
|
FindShow is {Silicon Valley|showName} on the list
|
||||||
|
|
||||||
|
FindShow if {How I Met Your Mother|showName} is on the list
|
||||||
|
FindShow if {Jessica Jones|showName} is on the list
|
||||||
|
FindShow if {Seinfeld|showName} is on the list
|
||||||
|
FindShow if {Better Call Saul|showName} is on the list
|
||||||
|
FindShow if {Parks and Recreation|showName} is on the list
|
||||||
|
FindShow if {Silicon Valley|showName} is on the list
|
||||||
|
|
||||||
|
FindShow is {How I Met Your Mother|showName} already
|
||||||
|
FindShow is {Jessica Jones|showName} already
|
||||||
|
FindShow is {Seinfeld|showName} already
|
||||||
|
FindShow is {Better Call Saul|showName} already
|
||||||
|
FindShow is {Parks and Recreation|showName} already
|
||||||
|
FindShow is {Silicon Valley|showName} already
|
||||||
|
|
||||||
|
FindShow if {How I Met Your Mother|showName} is already
|
||||||
|
FindShow if {Jessica Jones|showName} is already
|
||||||
|
FindShow if {Seinfeld|showName} is already
|
||||||
|
FindShow if {Better Call Saul|showName} is already
|
||||||
|
FindShow if {Parks and Recreation|showName} is already
|
||||||
|
FindShow if {Silicon Valley| showName} is already
|
132
lib/handlers.js
Normal file
132
lib/handlers.js
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
var _ = require('underscore');
|
||||||
|
var SickBeard = require('node-sickbeard');
|
||||||
|
var utils = require('./utils.js');
|
||||||
|
|
||||||
|
var WELCOME_DESCRIPTION = 'This skill allows you to manage your SickBeard movie list.';
|
||||||
|
var HELP_RESPONSE = ['You can ask SickBeard about the movies in your queue or add new movies',
|
||||||
|
'to it. Try asking "is Silicon Valley on the list?". If it\'s not and you want to add it, try',
|
||||||
|
'saying "add Silicon Valley".'].join(' ');
|
||||||
|
|
||||||
|
var config = require('dotenv').config();
|
||||||
|
var sb = new SickBeard({
|
||||||
|
url: config.SB_URL,
|
||||||
|
apikey: config.SB_API_KEY
|
||||||
|
});
|
||||||
|
|
||||||
|
function handleLaunchIntent(req, resp) {
|
||||||
|
resp
|
||||||
|
.say(WELCOME_DESCRIPTION)
|
||||||
|
.say(HELP_RESPONSE)
|
||||||
|
.send();
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleFindShowIntent(req, resp) {
|
||||||
|
var showName = req.slot('showName');
|
||||||
|
|
||||||
|
sb.cmd('shows').then(function (searchResp) {
|
||||||
|
var shows = searchResp.data;
|
||||||
|
var result = shows && Object.keys(shows).length ? _.find(shows, function (show) {
|
||||||
|
return show.show_name.toLowerCase().indexOf(showName.toLowerCase()) >= 0;
|
||||||
|
}) : null;
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
resp.say('Couldn\'t find ' + showName + ' queued for download. ');
|
||||||
|
|
||||||
|
sb.cmd('sb.searchtvdb', {name: showName}).then(function (searchResults) {
|
||||||
|
utils.sendSearchResponse(searchResults.data.results, resp);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
resp
|
||||||
|
.say(['It looks like', result.show_name, 'is already on your list.'].join(' '))
|
||||||
|
.send();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
//Async response
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleAddShowIntent(req, resp) {
|
||||||
|
var showName = req.slot('showName');
|
||||||
|
|
||||||
|
sb.cmd('sb.searchtvdb', {name: showName}).then(function (searchResults) {
|
||||||
|
utils.sendSearchResponse(searchResults.data.results, resp);
|
||||||
|
});
|
||||||
|
|
||||||
|
//Async response
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleYesIntent(req, resp) {
|
||||||
|
var promptData = req.session('promptData');
|
||||||
|
var show;
|
||||||
|
|
||||||
|
if (!promptData) {
|
||||||
|
console.log('Got a AMAZON.YesIntent but no promptData. Ending session.');
|
||||||
|
resp.send();
|
||||||
|
}
|
||||||
|
else if (promptData.yesAction === 'addShow') {
|
||||||
|
show = promptData.searchResults[0];
|
||||||
|
|
||||||
|
sb.cmd('show.addnew', {
|
||||||
|
tvdbid: show.tvdbid,
|
||||||
|
status: 'wanted'
|
||||||
|
}).then(function () {
|
||||||
|
resp
|
||||||
|
.say(promptData.yesResponse)
|
||||||
|
.send();
|
||||||
|
});
|
||||||
|
|
||||||
|
//Async response
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.log("Got an unexpected yesAction. PromptData:");
|
||||||
|
console.log(promptData);
|
||||||
|
resp.send();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleNoIntent(req, resp) {
|
||||||
|
var promptData = req.session('promptData');
|
||||||
|
|
||||||
|
if (!promptData) {
|
||||||
|
console.log('Got a AMAZON.YesIntent but no promptData. Ending session.');
|
||||||
|
resp.send();
|
||||||
|
}
|
||||||
|
else if (promptData.noAction === 'endSession') {
|
||||||
|
resp.say(promptData.noResponse).send();
|
||||||
|
}
|
||||||
|
else if (promptData.noAction === 'suggestNextShow') {
|
||||||
|
var shows = promptData.searchResults;
|
||||||
|
resp
|
||||||
|
.say(promptData.noResponse)
|
||||||
|
.session('promptData', utils.buildPrompt(shows.slice(1)))
|
||||||
|
.shouldEndSession(false)
|
||||||
|
.send();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.log("Got an unexpected noAction. PromptData:");
|
||||||
|
console.log(promptData);
|
||||||
|
resp.send();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCancelIntent(req, resp) {
|
||||||
|
resp.shouldEndSession(true).send();
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCancelIntent(req, resp) {
|
||||||
|
resp.say(HELP_RESPONSE).send();
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
handleFindShowIntent: handleFindShowIntent,
|
||||||
|
handleAddShowIntent: handleAddShowIntent,
|
||||||
|
handleYesIntent: handleYesIntent,
|
||||||
|
handleNoIntent: handleNoIntent,
|
||||||
|
handleCancelIntent: handleCancelIntent
|
||||||
|
};
|
15
lib/index.js
Normal file
15
lib/index.js
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
var handlers = require('./handlers.js');
|
||||||
|
|
||||||
|
var alexa = require('alexa-app');
|
||||||
|
var app = new alexa.app('sickBeard');
|
||||||
|
|
||||||
|
app.launch(handlers.handleLaunchIntent);
|
||||||
|
app.intent('FindShow', handlers.handleFindShowIntent);
|
||||||
|
app.intent('AddShow', handlers.handleAddShowIntent);
|
||||||
|
app.intent('AMAZON.YesIntent', handlers.handleYesIntent);
|
||||||
|
app.intent('AMAZON.NoIntent', handlers.handleNoIntent);
|
||||||
|
app.intent('AMAZON.CancelIntent', handlers.handleCancelIntent);
|
||||||
|
|
||||||
|
module.exports = app;
|
35
lib/utils.js
Normal file
35
lib/utils.js
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
function buildPrompt(shows) {
|
||||||
|
var promptData = {
|
||||||
|
searchResults: shows.slice(0, 5),
|
||||||
|
yesAction : 'addShow',
|
||||||
|
yesResponse: ['Added', shows[0].name, 'to your list of shows to download.'].join(' ')
|
||||||
|
};
|
||||||
|
|
||||||
|
if (shows.length > 1) {
|
||||||
|
promptData.noAction = 'suggestNextShow';
|
||||||
|
promptData.noResponse = 'Ok, did you mean ' + shows[1].name + '?';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
promptData.noAction = 'endSession';
|
||||||
|
promptData.noResponse = 'Ok. I\'m out of suggestions. Sorry about that.';
|
||||||
|
}
|
||||||
|
|
||||||
|
return promptData;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendSearchResponse(shows, resp) {
|
||||||
|
if(!shows || !shows.length) {
|
||||||
|
return resp.say('No show found for ' + showName).send();
|
||||||
|
}
|
||||||
|
|
||||||
|
resp
|
||||||
|
.say(['Add', shows[0].name, 'to your list?'].join(' '))
|
||||||
|
.session('promptData', buildPrompt(shows.slice(0, 5)))
|
||||||
|
.shouldEndSession(false)
|
||||||
|
.send();
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
buildPrompt: buildPrompt,
|
||||||
|
sendSearchResponse: sendSearchResponse
|
||||||
|
};
|
21
package.json
Normal file
21
package.json
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"name": "alexa-sickbeard",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "A skill to ask Alexa about your SickBeard queue.",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"bundle": "mkdir -p bundle && cp -r {.env,index.js,lib,node_modules} bundle/ && cd bundle && bestzip ../lambda.zip * .env && rm -rf ../bundle"
|
||||||
|
},
|
||||||
|
"author": "Joe Schmitt",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"alexa-app": "^2.3.2",
|
||||||
|
"dotenv": "^2.0.0",
|
||||||
|
"node-sickbeard": "0.0.1",
|
||||||
|
"underscore": "^1.8.3"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"bestzip": "^1.1.3",
|
||||||
|
"node-lambda": "^0.7.1"
|
||||||
|
}
|
||||||
|
}
|
27
test_events/couch_potato_add_show.json
Normal file
27
test_events/couch_potato_add_show.json
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"session": {
|
||||||
|
"sessionId": "SessionId.1234",
|
||||||
|
"application": {
|
||||||
|
"applicationId": "amzn1.echo-sdk-ams.app.1234"
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"userId": "amzn1.echo-sdk-account.1234"
|
||||||
|
},
|
||||||
|
"new": true
|
||||||
|
},
|
||||||
|
"request": {
|
||||||
|
"type": "IntentRequest",
|
||||||
|
"requestId": "EdwRequestId.1234",
|
||||||
|
"timestamp": "2016-01-01T00:00:00Z",
|
||||||
|
"intent": {
|
||||||
|
"name": "AddShow",
|
||||||
|
"slots": {
|
||||||
|
"showName": {
|
||||||
|
"name": "showName",
|
||||||
|
"value": "silicon"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"version": "1.0"
|
||||||
|
}
|
26
test_events/couch_potato_find_show.json
Normal file
26
test_events/couch_potato_find_show.json
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"session": {
|
||||||
|
"sessionId": "SessionId.1234",
|
||||||
|
"application": {
|
||||||
|
"applicationId": "amzn1.echo-sdk-ams.app.1234"
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"userId": "amzn1.echo-sdk-account.1234"
|
||||||
|
},
|
||||||
|
"new": true
|
||||||
|
},
|
||||||
|
"request": {
|
||||||
|
"type": "IntentRequest",
|
||||||
|
"requestId": "EdwRequestId.1234",
|
||||||
|
"timestamp": "2016-01-01T00:00:00Z",
|
||||||
|
"intent": {
|
||||||
|
"name": "FindShow",
|
||||||
|
"slots": {
|
||||||
|
"showName": {
|
||||||
|
"name": "showName",
|
||||||
|
"value": "silicon valley"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
50
test_events/yes_reprompt.json
Normal file
50
test_events/yes_reprompt.json
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
{
|
||||||
|
"session": {
|
||||||
|
"sessionId": "SessionId.1234",
|
||||||
|
"application": {
|
||||||
|
"applicationId": "amzn1.echo-sdk-ams.app.1234"
|
||||||
|
},
|
||||||
|
"attributes": {
|
||||||
|
"promptData": {
|
||||||
|
"searchResults": [
|
||||||
|
{
|
||||||
|
"name": "Silicon Wadi",
|
||||||
|
"tvdbid": 268854
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"first_aired": "2014-04-06",
|
||||||
|
"name": "Silicon Valley",
|
||||||
|
"tvdbid": 277165
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"first_aired": "2012-11-01",
|
||||||
|
"name": "Silicon Valley Rebels",
|
||||||
|
"tvdbid": 283723
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"first_aired": "2012-11-05",
|
||||||
|
"name": "Start-ups: Silicon Valley",
|
||||||
|
"tvdbid": 263541
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"yesAction": "addShow",
|
||||||
|
"yesResponse": "Added Silicon Wadi to your list of shows to download.",
|
||||||
|
"noAction": "suggestNextShow",
|
||||||
|
"noResponse": "Ok, did you mean Silicon Valley?"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"userId": "amzn1.echo-sdk-account.1234"
|
||||||
|
},
|
||||||
|
"new": false
|
||||||
|
},
|
||||||
|
"request": {
|
||||||
|
"type": "IntentRequest",
|
||||||
|
"requestId": "EdwRequestId.1234",
|
||||||
|
"timestamp": "2016-01-01T00:00:00Z",
|
||||||
|
"intent": {
|
||||||
|
"name": "AMAZON.NoIntent",
|
||||||
|
"slots": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user