Update the codebase to use ES6 and support Node v4.3 on Lambda (#13)

Fixes #6

This required a few changes to the code base. First of all, we need to update our lambda function to use Node v4.3. This can be done in the Lambda console on AWS, and is also done in the default.env file. Next, since Node 4.3 doesn't offer full ES6 support yet, we need to run our source code through babel to transform it to be ES5-friendly. The ES6 source now lives in the src/ directory, and the transformed js is output into a dist/ directory. The dist/ package is what ends up getting uploaded to lambda.

One last issue is that node-lambda doesn't work with ES6 either, so we need to make sure it runs against the files in the dist/ directory instead. You can use the test-lambda npm script to do this automatically for you by executing npm run test-lambda in the terminal. This will trigger running babel to transform the js and then running node-lambda against the newly transformed files in dist/.
这个提交包含在:
Joseph J. Schmitt 2016-10-29 22:04:44 -04:00 提交者 GitHub
父节点 e09ee66d1c
当前提交 e31ac662a6
共有 13 个文件被更改,包括 206 次插入183 次删除

4
.babelrc 普通文件
查看文件

@ -0,0 +1,4 @@
{
"presets": ["es2015"],
"plugins": ["transform-runtime"]
}

1
.gitignore vendored
查看文件

@ -2,3 +2,4 @@ deploy.env
lambda.zip
node_modules/
.env
dist/

查看文件

@ -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

查看文件

@ -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

查看文件

@ -26,6 +26,9 @@
},
{
"intent": "AMAZON.CancelIntent"
},
{
"intent": "AMAZON.HelpIntent"
}
]
}

查看文件

@ -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
};

查看文件

@ -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;

查看文件

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

查看文件

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

128
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();
}

31
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;

7
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';

查看文件

@ -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
};