diff --git a/.gitignore b/.gitignore
index 0beef0c..136e8af 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,3 +4,4 @@ subway.zip
.env
downloads
.vscode
+dynamodb_local
diff --git a/handlers/store-favorite-line.js b/handlers/store-favorite-line.js
new file mode 100644
index 0000000..c979785
--- /dev/null
+++ b/handlers/store-favorite-line.js
@@ -0,0 +1,14 @@
+const fetchMTAStatus = require('../services/fetch-mta-status.js');
+const closestLineMatcher = require('../utilities/closest-line-matcher.js');
+
+module.exports = function() {
+ let heardNameGroup = this.event.request.intent.slots.subwayLineOrGroup.value;
+
+ if(!nameGroup) { this.emit(':tell', "Sorry, I didn't hear a subway line or group I recognized") };
+
+ fetchMTAStatus(statuses => {
+ let closestLine = closestLineMatcher(statuses, 'nameGroup', heardNameGroup);
+
+ this.emit('tell', `Your favorite line is ${closestLine.nameGroup}`);
+ });
+}
diff --git a/index.js b/index.js
index 240be2f..ca33394 100644
--- a/index.js
+++ b/index.js
@@ -5,35 +5,27 @@ const uuidv4 = require("uuid/v4");
var env = process.env.NODE_ENV || 'development';
const logRequests = process.env.LOG_REQUESTS === 'true';
+const applicationId = process.env.APPLICATION_ID;
if(env !== 'production') {
require('dotenv').load({ path: '.env.' + env });
}
-var applicationId = process.env.APPLICATION_ID;
-var mtaStatusURL = process.env.MTA_STATUS_URL;
+const _ = require('lodash');
+const Alexa = require('alexa-sdk');
-var _ = require('lodash');
-var levenshtein = require('fast-levenshtein');
-var Alexa = require('alexa-sdk');
-var currentMTAStatus = require('./current-mta-status.js');
-var statusToSpeech = require('./status-to-speech.js');
+const statusToSpeech = require('./services/status-to-speech.js');
+const fetchMTAStatus = require('./services/fetch-mta-status.js');
+const closestLineMatcher = require('./utilities/closest-line-matcher.js');
-var fetchStatus = function(callback) {
- return fetch(mtaStatusURL).then(function(response) {
- return response.text()
- }).then(function(body) {
- currentMTAStatus(body, callback);
- });
-};
+// Handlers
+const storeFavoriteLineHandler = require('./handlers/store-favorite-line.js');
const affectedServiceStatusesBuilder = (statuses) => {
- let notGoodService = status => status.status !== 'GOOD SERVICE';
-
- let affectedServices = statuses
- .filter(notGoodService)
+ let withServiceIssues = status => status.status !== 'GOOD SERVICE';
- return _(affectedServices)
+ return _(statuses)
+ .filter(withServiceIssues)
.groupBy('status')
.map((lines, status, collection) => {
lines = lines.map(status => status.nameGroup);
@@ -42,7 +34,7 @@ const affectedServiceStatusesBuilder = (statuses) => {
};
var fullStatusUpdateHandler = function() {
- fetchStatus(statuses => {
+ fetchMTAStatus(statuses => {
let affectedServiceStatuses = affectedServiceStatusesBuilder(statuses);
if(affectedServiceStatuses.length === 0) {
@@ -58,22 +50,13 @@ var handlers = {
statusOfLine: function () {
var self = this;
var nameGroup = self.event.request.intent.slots.subwayLineOrGroup.value;
- var closestStatus = null;
var badQueryResponse = function() {
self.emit(':tell', "Sorry, I didn't hear a subway line I understand");
}
if(nameGroup) {
- fetchStatus(function(statuses) {
- if(nameGroup.length > 1) {
- closestStatus = _.minBy(statuses, function(status) {
- return levenshtein.get(status.nameGroup, nameGroup.toUpperCase());
- });
- } else {
- closestStatus = _.find(statuses, function(status) {
- return status.nameGroup.search(nameGroup.toUpperCase()) !== -1;
- });
- }
+ fetchMTAStatus(function(statuses) {
+ let closestStatus = closestLineMatcher(statuses, 'nameGroup', nameGroup);
if(closestStatus) {
if(closestStatus.description) {
@@ -92,11 +75,12 @@ var handlers = {
},
fullStatusUpdate: fullStatusUpdateHandler,
- Unhandled: fullStatusUpdateHandler
+ storeFavoriteLine: storeFavoriteLineHandler,
+ Unhandled: fullStatusUpdateHandler,
};
exports.flashBriefingHandler = (event, context, callback) => {
- fetchStatus(statuses => {
+ fetchMTAStatus(statuses => {
let affectedServiceStatuses = affectedServiceStatusesBuilder(statuses);
let message;
diff --git a/intents.json b/intents.json
index 1a49a27..435972b 100644
--- a/intents.json
+++ b/intents.json
@@ -11,6 +11,15 @@
{
"intent": "fullStatusUpdate",
"slots": []
+ },
+ {
+ "intent": "storeFavoriteLine",
+ "slots":[
+ {
+ "name": "subwayLineOrGroup",
+ "type": "SUBWAY_LINE_OR_GROUP"
+ }
+ ]
}
]
}
diff --git a/current-mta-status.js b/parsers/current-mta-status.js
similarity index 79%
rename from current-mta-status.js
rename to parsers/current-mta-status.js
index 38db4d2..0aa8a93 100644
--- a/current-mta-status.js
+++ b/parsers/current-mta-status.js
@@ -1,5 +1,5 @@
-var parseXML = require('xml2js').parseString;
-var serviceDetailsCleaner = require('./service-details-cleaner');
+const parseXML = require('xml2js').parseString;
+const serviceDetailsCleaner = require('./service-details-cleaner');
module.exports = function(mtaStatusXML, callback) {
parseXML(mtaStatusXML, { normalize: true, trim: true }, function(error, results) {
diff --git a/parsers/service-details-cleaner.js b/parsers/service-details-cleaner.js
new file mode 100644
index 0000000..7befb85
--- /dev/null
+++ b/parsers/service-details-cleaner.js
@@ -0,0 +1,12 @@
+const sanitize = require('sanitize-html');
+const MATCHERS = {
+ br: /
\s*/g
+}
+
+module.exports = function(detailsText) {
+ let text = detailsText.replace(MATCHERS.br, '\n'); // BRs to newline
+
+ return sanitize(text, {
+ allowedTags: []
+ });
+};
diff --git a/service-details-cleaner.js b/service-details-cleaner.js
deleted file mode 100644
index ae45b64..0000000
--- a/service-details-cleaner.js
+++ /dev/null
@@ -1,12 +0,0 @@
-var sanitize = require('sanitize-html');
-var MATCHERS = {
- br: /
\s*/g
-}
-
-module.exports = function(detailsText) {
- var text = detailsText.replace(MATCHERS.br, '\n'); // BRs to linebreaks
-
- return sanitize(text, {
- allowedTags: []
- });
-};
diff --git a/services/fetch-mta-status.js b/services/fetch-mta-status.js
new file mode 100644
index 0000000..1adc8d8
--- /dev/null
+++ b/services/fetch-mta-status.js
@@ -0,0 +1,8 @@
+const MTA_STATUS_URL = process.env.MTA_STATUS_URL;
+const currentMTAStatus = require('../parsers/current-mta-status.js');
+
+module.exports = function(callback) {
+ return fetch(MTA_STATUS_URL)
+ .then(response => response.text())
+ .then(body => currentMTAStatus(body, callback));
+};
diff --git a/status-to-speech.js b/services/status-to-speech.js
similarity index 100%
rename from status-to-speech.js
rename to services/status-to-speech.js
diff --git a/tests/index-test.js b/tests/index-test.js
index ee0883c..af850f5 100644
--- a/tests/index-test.js
+++ b/tests/index-test.js
@@ -53,7 +53,7 @@ test.serial('handling "ask subway status to check on the line?" an
fetchMock.restore();
});
-test.serial('handling "ask subway status to check on the line?" and the service the service is good', async t => {
+test.serial('handling "ask subway status to check on the line?" and the service is good', async t => {
t.plan(2);
const statusOfLineEvent = JSON.parse(fs.readFileSync(process.cwd() + '/tests/fixtures/events/status-of-line.json'));
@@ -97,6 +97,7 @@ test.serial('handling "ask subway status for an update and there are bad service
.expectSucceed(r => r);
let speechMarkup = result.response.outputSpeech.ssml;
+
t.true(speechMarkup.search("1-2-3, B-D-F-M, J-Z, N-Q-R") !== -1);
t.true(speechMarkup.search('A-C-E') !== -1);
t.true(speechMarkup.search('Good service on all other lines. ') !== -1);
diff --git a/tests/service-details-cleaner-test.js b/tests/service-details-cleaner-test.js
index 273fc54..424f922 100644
--- a/tests/service-details-cleaner-test.js
+++ b/tests/service-details-cleaner-test.js
@@ -2,7 +2,7 @@ import test from 'ava';
import fs from 'fs';
import { parseString } from 'xml2js'
-import { default as serviceDetailsCleaner } from '../service-details-cleaner.js';
+import { default as serviceDetailsCleaner } from '../parsers/service-details-cleaner.js';
test('removes all linebreaks', t => {
t.plan(1);
diff --git a/tests/status-to-speech-test.js b/tests/status-to-speech-test.js
index 3e31703..39d1213 100644
--- a/tests/status-to-speech-test.js
+++ b/tests/status-to-speech-test.js
@@ -1,6 +1,6 @@
import test from 'ava';
-import statusToSpeech from '../status-to-speech.js';
+import statusToSpeech from '../services/status-to-speech.js';
test('Given a service status, it builds the right speech"', t => {
t.plan(4);
diff --git a/utilities/closest-line-matcher.js b/utilities/closest-line-matcher.js
new file mode 100644
index 0000000..4ac7429
--- /dev/null
+++ b/utilities/closest-line-matcher.js
@@ -0,0 +1,17 @@
+const _ = require('lodash');
+const levenshtein = require('fast-levenshtein');
+
+module.exports = function(statuses, key, potentialValue) {
+ let closetStatus;
+
+ if(potentialValue.length > 1) {
+ closestStatus = _.minBy(statuses, function(status) {
+ return levenshtein.get(status[key], potentialValue.toUpperCase());
+ });
+ } else {
+ closestStatus = _.find(statuses, function(status) {
+ return status[key].search(potentialValue.toUpperCase()) !== -1;
+ });
+ }
+ return closestStatus;
+}
diff --git a/utterances.txt b/utterances.txt
index f9d0b0d..45143fa 100644
--- a/utterances.txt
+++ b/utterances.txt
@@ -5,3 +5,9 @@ statusOfLine what is the status of {subwayLineOrGroup}
fullStatusUpdate for a service update
fullStatusUpdate for a full service update
fullStatusUpdate for an update
+storeFavoriteLine to track the {subwayLineOrGroup}
+removeFavoriteLine to forget the {subwayLineOrGroup}
+resetFavoriteLine to forget all my tracked lines
+checkFavoriteLines to check my favorite lines
+checkFavoriteLines what is the status of my favorite lines?
+checkFavoriteLines to check my commute