SickBeard Alexa skill 1.0

This commit is contained in:
Joseph Schmitt 2016-03-27 16:22:50 -04:00
commit 39b8d15540
14 changed files with 472 additions and 0 deletions

12
.env Normal file
View 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
View File

@ -0,0 +1,3 @@
deploy.env
lambda.zip
node_modules/

55
README.md Normal file
View 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
View 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
View File

@ -0,0 +1,4 @@
'use strict';
var app = require('./lib');
exports.handler = app.lambda();

View 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"
}
]
}

View 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
View 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
View 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
View 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
View 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"
}
}

View 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"
}

View 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"
}
}
}
}
}

View 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": {}
}
}
}