diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000..3ca1979 --- /dev/null +++ b/.babelrc @@ -0,0 +1,4 @@ +{ + "presets": ["es2015"], + "plugins": ["transform-runtime"] +} diff --git a/.gitignore b/.gitignore index 17aa83e..9607674 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ deploy.env lambda.zip node_modules/ .env +dist/ diff --git a/README.md b/README.md index 275a61c..37b0119 100644 --- a/README.md +++ b/README.md @@ -23,8 +23,8 @@ should point to your Couch Potato server, and `CP_API_KEY` which should have you 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. +that directory and overwrite the contents of `event.json`. Make sure you run `npm install` from the +command line to get the the latest npm packages, and then run `npm run test-lambda`. ## Setting up the Skill diff --git a/default.env b/default.env index c90e982..b41551a 100644 --- a/default.env +++ b/default.env @@ -7,6 +7,6 @@ AWS_MODE=event AWS_MEMORY_SIZE=128 AWS_TIMEOUT=10 AWS_DESCRIPTION= -AWS_RUNTIME=nodejs +AWS_RUNTIME=nodejs4.3 CP_URL=http://url-to-couch-potato-server CP_API_KEY=APIKEY diff --git a/interaction_model/intent_schema.json b/interaction_model/intent_schema.json index 28620ea..99460ae 100644 --- a/interaction_model/intent_schema.json +++ b/interaction_model/intent_schema.json @@ -26,6 +26,9 @@ }, { "intent": "AMAZON.CancelIntent" + }, + { + "intent": "AMAZON.HelpIntent" } ] } diff --git a/lib/handlers.js b/lib/handlers.js deleted file mode 100644 index 9e26f10..0000000 --- a/lib/handlers.js +++ /dev/null @@ -1,132 +0,0 @@ -'use strict'; - -var CouchPotato = require('node-couchpotato'); -var utils = require('./utils.js'); - -var WELCOME_DESCRIPTION = 'This skill allows you to manage your Couch Potato movie list.'; -var HELP_RESPONSE = ['You can ask Couch Potato about the movies in your queue or add new movies', - 'to it. Try asking "is The Godfather on the list?". If it\'s not and you want to add it, try', - 'saying "add The Godfather".'].join(' '); -var CANCEL_RESPONSE = 'Exiting Couch Potato'; - -var config = require('dotenv').config(); -var cp = new CouchPotato({ - url: config.CP_URL, - apikey: config.CP_API_KEY, - debug:true -}); - -function handleLaunchIntent(req, resp) { - resp - .say(WELCOME_DESCRIPTION) - .say(HELP_RESPONSE) - .send(); -} - -function handleFindMovieIntent(req, resp) { - var movieName = req.slot('movieName'); - - cp.movie.list({search: movieName, limit_offset: 5}).then(function (searchResp) { - var movies = searchResp.movies; - var result; - - if (!movies || !movies.length) { - resp.say('Couldn\'t find ' + movieName + ' queued for download. '); - - cp.movie.search(movieName).then(function (searchResults) { - utils.sendSearchResponse(searchResults, resp); - }); - } - else { - result = movies[0].info; - resp - .say([ - 'It looks like', result.original_title, - '(' + result.year + ')', 'is already on your list.' - ].join(' ')) - .send(); - } - }); - - //Async response - return false; -} - -function handleAddMovieIntent(req, resp) { - var movieName = req.slot('movieName'); - - cp.movie.search(movieName,5).then(function (movies) { - movies = utils.formatSearchResults(movies); - utils.sendSearchResponse(movies, movieName, resp); - }); - - //Async response - return false; -} - -function handleYesIntent(req, resp) { - var promptData = req.session('promptData'); - var movie; - - if (!promptData) { - console.log('Got a AMAZON.YesIntent but no promptData. Ending session.'); - resp.send(); - } - else if (promptData.yesAction === 'addMovie') { - movie = promptData.searchResults[0]; - - cp.movie.add({ - title: movie.titles[0], - identifier: movie.imdb - }).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 === 'suggestNextMovie') { - var movies = promptData.searchResults; - resp - .say(promptData.noResponse) - .session('promptData', utils.buildPrompt(movies.slice(1))) - .shouldEndSession(false) - .send(); - } - else { - console.log("Got an unexpected noAction. PromptData:"); - console.log(promptData); - resp.send(); - } -} - -function handleCancelIntent(req, resp) { - resp.say(CANCEL_RESPONSE).shouldEndSession(true).send(); -} - -module.exports = { - handleFindMovieIntent: handleFindMovieIntent, - handleAddMovieIntent: handleAddMovieIntent, - handleYesIntent: handleYesIntent, - handleNoIntent: handleNoIntent, - handleCancelIntent: handleCancelIntent -}; diff --git a/lib/index.js b/lib/index.js deleted file mode 100644 index 4579cef..0000000 --- a/lib/index.js +++ /dev/null @@ -1,15 +0,0 @@ -'use strict'; - -var handlers = require('./handlers.js'); - -var alexa = require('alexa-app'); -var app = new alexa.app('couchPotato'); - -app.launch(handlers.handleLaunchIntent); -app.intent('FindMovie', handlers.handleFindMovieIntent); -app.intent('AddMovie', handlers.handleAddMovieIntent); -app.intent('AMAZON.YesIntent', handlers.handleYesIntent); -app.intent('AMAZON.NoIntent', handlers.handleNoIntent); -app.intent('AMAZON.CancelIntent', handlers.handleCancelIntent); - -module.exports = app; diff --git a/package.json b/package.json index 3715a62..b4cfeda 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,13 @@ { "name": "alexa-couchpotato", - "version": "1.0.0", + "version": "1.0.4", "description": "A skill to ask Alexa about your Couch Potato queue.", - "main": "index.js", + "main": "src/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" + "build": "babel src -d dist", + "zip": "cp -r {.env,deploy.env,package.json} dist/; cd dist; npm install --production; bestzip ../lambda.zip * .env;", + "bundle": "npm run build; npm run zip;", + "test-lambda": "npm run build; node-lambda run --handler dist/index.handler" }, "author": "Joe Schmitt", "license": "MIT", @@ -14,7 +17,11 @@ "node-couchpotato": "^0.1.4" }, "devDependencies": { + "babel-cli": "^6.7.5", + "babel-core": "^6.7.6", + "babel-plugin-transform-runtime": "^6.7.5", + "babel-preset-es2015": "^6.6.0", "bestzip": "^1.1.3", - "node-lambda": "^0.7.1" + "node-lambda": "^0.8.11" } } diff --git a/index.js b/src/index.js similarity index 62% rename from index.js rename to src/index.js index 5581cfc..e167aeb 100644 --- a/index.js +++ b/src/index.js @@ -1,4 +1,5 @@ 'use strict'; -var app = require('./lib'); +import app from './lib'; + exports.handler = app.lambda(); diff --git a/src/lib/handlers.js b/src/lib/handlers.js new file mode 100644 index 0000000..77f043b --- /dev/null +++ b/src/lib/handlers.js @@ -0,0 +1,128 @@ +'use strict'; + +import CouchPotato from 'node-couchpotato'; + +import { + buildPrompt, + sendSearchResponse, + formatSearchResults +} from './utils.js'; + +import { + WELCOME_DESCRIPTION, + HELP_RESPONSE, + CANCEL_RESPONSE +} from './responses.js'; + +const config = require('dotenv').config(); +const cp = new CouchPotato({ + url: config.CP_URL, + apikey: config.CP_API_KEY, + debug:true +}); + +export default function handleLaunchIntent(req, resp) { + resp + .say(WELCOME_DESCRIPTION) + .say(HELP_RESPONSE) + .send(); +} + +export function handleFindMovieIntent(req, resp) { + const movieName = req.slot('movieName'); + + cp.movie.list({search: movieName, limit_offset: 5}).then(function (searchResp) { + const movies = searchResp.movies; + + if (!movies || !movies.length) { + resp.say(`Couldn't find ${movieName} queued for download. `); + + cp.movie.search(movieName).then(function (searchResults) { + sendSearchResponse(searchResults, resp); + }); + } + else { + const result = movies[0].info; + resp + .say(`It looks like ${result.original_title} (${result.year}) is already on your list.`) + .send(); + } + }); + + //Async response + return false; +} + +export function handleAddMovieIntent(req, resp) { + const movieName = req.slot('movieName'); + + cp.movie.search(movieName, 5).then(function (movies) { + movies = formatSearchResults(movies); + sendSearchResponse(movies, movieName, resp); + }); + + //Async response + return false; +} + +export function handleYesIntent(req, resp) { + const promptData = req.session('promptData'); + + if (!promptData) { + console.log('Got a AMAZON.YesIntent but no promptData. Ending session.'); + resp.send(); + } + else if (promptData.yesAction === 'addMovie') { + const movie = promptData.searchResults[0]; + + cp.movie.add({ + title: movie.titles[0], + identifier: movie.imdb + }).then(function () { + resp + .say(promptData.yesResponse) + .send(); + }); + + //Async response + return false; + } + else { + console.log('Got an unexpected yesAction. PromptData:'); + console.log(promptData); + resp.send(); + } +} + +export function handleNoIntent(req, resp) { + const promptData = req.session('promptData'); + + if (!promptData) { + console.log('Got a AMAZON.NoIntent but no promptData. Ending session.'); + resp.send(); + } + else if (promptData.noAction === 'endSession') { + resp.say(promptData.noResponse).send(); + } + else if (promptData.noAction === 'suggestNextMovie') { + const movies = promptData.searchResults; + resp + .say(promptData.noResponse) + .session('promptData', buildPrompt(movies.slice(1))) + .shouldEndSession(false) + .send(); + } + else { + console.log('Got an unexpected noAction. PromptData:'); + console.log(promptData); + resp.send(); + } +} + +export function handleCancelIntent(req, resp) { + resp.say(CANCEL_RESPONSE).shouldEndSession(true).send(); +} + +export function handleHelpIntent(req, resp) { + resp.say(HELP_RESPONSE).send(); +} diff --git a/src/lib/index.js b/src/lib/index.js new file mode 100644 index 0000000..c952042 --- /dev/null +++ b/src/lib/index.js @@ -0,0 +1,31 @@ +'use strict'; + +import Alexa from 'alexa-app'; + +import handleLaunchIntent, { + handleFindMovieIntent, + handleAddMovieIntent, + handleYesIntent, + handleNoIntent, + handleCancelIntent, + handleHelpIntent +} from './handlers.js'; + +const app = new Alexa.app('couchPotato'); + +app.launch(handleLaunchIntent); +app.intent('FindMovie', handleFindMovieIntent); +app.intent('AddMovie', handleAddMovieIntent); +app.intent('AMAZON.YesIntent', handleYesIntent); +app.intent('AMAZON.NoIntent', handleNoIntent); +app.intent('AMAZON.CancelIntent', handleCancelIntent); +app.intent('AMAZON.HelpIntent', handleHelpIntent); + +app.post = function(request, response, type, exception) { + if (exception) { + // Always turn an exception into a successful response + response.clear().say('An error occured: ' + exception).send(); + } +}; + +export default app; diff --git a/src/lib/responses.js b/src/lib/responses.js new file mode 100644 index 0000000..937a2e6 --- /dev/null +++ b/src/lib/responses.js @@ -0,0 +1,7 @@ +export const WELCOME_DESCRIPTION = 'This skill allows you to manage your Couch Potato movie list.'; + +export const HELP_RESPONSE = ['You can ask Couch Potato about the movies in your queue or add new movies', + 'to it. Try asking "is The Godfather on the list?". If it\'s not and you want to add it, try', + 'saying "add The Godfather".'].join(' '); + +export const CANCEL_RESPONSE = 'Exiting Couch Potato'; diff --git a/lib/utils.js b/src/lib/utils.js similarity index 54% rename from lib/utils.js rename to src/lib/utils.js index 7753a2a..e17d48c 100644 --- a/lib/utils.js +++ b/src/lib/utils.js @@ -1,5 +1,5 @@ -function buildPrompt(movies) { - var promptData = { +export function buildPrompt(movies) { + const promptData = { searchResults: movies, yesAction : 'addMovie', yesResponse: 'Added ' + movies[0].original_title + ' (' + movies[0].year + ')' + ' to your list of movies to download.' @@ -17,9 +17,8 @@ function buildPrompt(movies) { return promptData; } -function sendSearchResponse(movies, movieName, resp) { - - if(!movies || !movies.length > 0) { +export function sendSearchResponse(movies, movieName, resp) { + if (!movies || !movies.length > 0) { return resp.say('No movie found for ' + movieName).send(); } @@ -30,29 +29,18 @@ function sendSearchResponse(movies, movieName, resp) { .send(); } -function formatSearchResults(movies) { - - var newMovies = []; - - if (movies != undefined) { - - for (var i = 0; i < movies.length; i++) { - - newMovies.push({ - original_title: movies[i].original_title, - in_library: movies[i].in_library, - year: movies[i].year, - titles: movies[i].titles, - imdb: movies[i].imdb - }); +export function formatSearchResults(movies) { + if (movies) { + return movies.map((movie) => { + return { + original_title: movie.original_title, + in_library: movie.in_library, + year: movie.year, + titles: movie.titles, + imdb: movie.imdb } - } + }); + } - return newMovies; + return []; } - -module.exports = { - buildPrompt: buildPrompt, - sendSearchResponse: sendSearchResponse, - formatSearchResults: formatSearchResults -};