'use strict';
var request = require('request');
var os = require('os');
/**
* This sample demonstrates a skill built with the Amazon Alexa Skills Kit.
* The Intent Schema, Custom Slots, and Sample Utterances for this skill, as well as
* testing instructions are located at http://lirrbits.com/alexa
*/
//Start: Initialize cache, so that warm starts can take advantage of it being populated.
var cache;
var thinkIAmOk = true;
let apiKey = process.env.traintime_key;
function buildCache() {
request("https://traintime.lirr.org/api/StationsAll?api_key=" + apiKey, function(err, response, body){
if (err) {
console.log('ran into error building cache');
setTimeout(function() {buildCache(); }, 10);
return;
}
cache = JSON.parse(body);
console.log('cache set up');
});
}
var none = buildCache();
//End: Initialize cache, so that warm starts can take advantage of it being populated.
// --------------- Helpers that build all of the responses -----------------------
function buildSpeechletResponse(title, output, repromptText, shouldEndSession) {
return {
outputSpeech: {
type: 'PlainText',
text: output,
},
card: {
type: 'Standard',
title: 'LIRRBits.com',
text: `${output}`,
image: {
smallImageUrl: 'https://brucel.ee/smallLogoLB.png',
largeImageUrl: 'https://brucel.ee/largeLogoLB.png'
}
},
reprompt: {
outputSpeech: {
type: "PlainText",
text: repromptText
}
},
shouldEndSession,
};
}
function buildResponse(sessionAttributes, speechletResponse) {
return {
version: '1.0',
sessionAttributes,
response: speechletResponse,
};
}
// --------------- Functions that control the skill's behavior -----------------------
function getWelcomeResponse(callback) {
// If we wanted to initialize the session to have some attributes we could add those here.
const sessionAttributes = {};
const cardTitle = 'Welcome';
const speechOutput = 'Welcome to lir bits. What would you like to know?';
callback(sessionAttributes,
buildSpeechletResponse(cardTitle, speechOutput, 'You can ask lir bits about the next trains from one station to another, or to give station information for a given station.', false));
}
function handleSessionEndRequest(callback) {
const cardTitle = 'Session Ended';
const speechOutput = 'Have a nice day!';
// Setting this to true ends the session and exits the skill.
const shouldEndSession = true;
callback({}, buildSpeechletResponse(cardTitle, speechOutput, null, shouldEndSession));
}
// --------------- Events -----------------------
/**
* Called when the session starts.
*/
function onSessionStarted(sessionStartedRequest, session) {
console.log(`onSessionStarted requestId=${sessionStartedRequest.requestId}, sessionId=${session.sessionId}`);
}
/**
* Called when the user launches the skill without specifying what they want.
*/
function onLaunch(launchRequest, session, callback) {
console.log(`onLaunch requestId=${launchRequest.requestId}, sessionId=${session.sessionId}`);
// Dispatch to your skill's launch.
getWelcomeResponse(callback);
}
/**
* Given StationOne and StationTwo information in an intent, as well as an api
* key, give either full, extended information about the next few trains, or
* just the departure times.
*/
function getTimes(intent, full, callback) {
if (!cache) {
console.log('still warming...');
setTimeout(function() {getTimes(intent, full, callback);}, 25);
return;
}
//Request all stations from the API, so that we can map a station name to its
//abbreviation
var normalizedStationOne = intent.slots.StationOne.value && intent.slots.StationOne.value.toLowerCase() || "";
var normalizedStationTwo = intent.slots.StationTwo.value && intent.slots.StationTwo.value.toLowerCase() || "";
var stations = cache;
var stationOneAbbr;
var stationTwoAbbr;
for (var i in stations.Stations) {
cache[stations.Stations[i].ABBR] = stations.Stations[i].NAME;
if (stations.Stations[i].NAME.toLowerCase() === normalizedStationOne){
stationOneAbbr = stations.Stations[i].ABBR;
}
if (stations.Stations[i].NAME.toLowerCase() === normalizedStationTwo){
stationTwoAbbr = stations.Stations[i].ABBR;
}
}
if (!stationOneAbbr || !stationTwoAbbr) {
//If we can't find the station, give the user information that we could
//not find the station.
callback({},
buildSpeechletResponse("card", "I was unable to determine times from " + normalizedStationOne + " to " + normalizedStationTwo, 'reprompt', true));
} else {
//Otherwise, ask the API for the metadata around the next few trains from
//the first station to the second
request("https://traintime.lirr.org/api/TrainTime?api_key=" + apiKey + "&startsta=" + stationOneAbbr + "&endsta=" + stationTwoAbbr, function(err, response, body){
if (err) {
callback({},
buildSpeechletResponse("card", "I ran into an error trying to determine times from " + normalizedStationOne + " to " + normalizedStationTwo, 'reprompt', true));
return;
}
var outs = JSON.parse(body);
var info = "";
//Return up to five trips, to avoid overwhelming the user
for (var k = 0; k < outs.TRIPS.length && k < 5; k++) {
//Give the start time by looking at the first leg and the first stop
info = info + outs.TRIPS[k].LEGS[0].STOPS[0].TIME + ", ";
//User may not want the full set of information
if (!full){
continue;
}
//Peak and offpeak denote cost differences in ticket prices
if (outs.TRIPS[k].PEAK_INDICATOR === 1) {
info = info + " Peak, ";
} else {
info = info + " Off Peak, ";
}
let legs = outs.TRIPS[k].LEGS;
//Check for whether one or more connections are needed, and add that
//information to the response
if (outs.TRIPS[k].LEGS.length !== 1) {
for (var legIndex = 0; legIndex < outs.TRIPS[k].LEGS.length - 1; legIndex++) {
let leg = legs[legIndex];
info = info + " Connecting at " + cache[leg.STOPS[leg.STOPS.length - 1].STATION] + ", ";
}
}
let finalLeg = legs[legs.length - 1];
let finalStop = finalLeg.STOPS[finalLeg.STOPS.length - 1];
info = info + finalStop.TIME + " Arrival. ";
}
if (info.length > 0) {
info = info.substring(0, info.length - 2);
info = "These are the next departures from " + normalizedStationOne + " to " + normalizedStationTwo + ": " + info ;
} else {
info = "I was unable to find information from " + normalizedStationOne + " to " + normalizedStationTwo;
}
callback({},
buildSpeechletResponse("card", info, 'reprompt', true));
});
}
}
function getStationInformation(intent, callback) {
if (!cache) {
console.log('still warming...');
setTimeout(function() {getStationInformation(intent, callback);}, 25);
return;
}
var stations = cache;
for (var i in stations.Stations) {
if (stations.Stations[i].NAME.toLowerCase() === intent.slots.Station.value){
let ticketMachine = stations.Stations[i].TICKETMACHINE;
if (ticketMachine.indexOf('None') === 0) {
ticketMachine = "No ticket machine information is listed for this station."
}
callback({},
buildSpeechletResponse("card", "The station is located at: " + stations.Stations[i].LOCATION + ". " + ticketMachine, 'reprompt', true));
return;
}
}
callback({},
buildSpeechletResponse("card", "I wasn't able to find station information for " + intent.slots.Station.value, 'reprompt', true));
}
/**
* Called when the user specifies an intent for this skill.
*/
function onIntent(intentRequest, session, callback) {
console.log(`onIntent requestId=${intentRequest.requestId}, sessionId=${session.sessionId}`);
const intent = intentRequest.intent;
const intentName = intentRequest.intent.name;
// Dispatch to your skill's intent handlers
console.log(intentName);
if (intentName === 'GetStationInformation') {
getStationInformation(intent, callback);
} else if (intentName === 'GetFullTimesInformation') {
getTimes(intent, true, callback);
} else if (intentName === 'GetTimesInformation') {
getTimes(intent, false, callback);
} else if (intentName === 'AMAZON.HelpIntent') {
callback({},
buildSpeechletResponse('Help', 'You can ask lir bits about the next trains from one station to another, or to give station information for a given station.', 'What would you like to ask lir bits?', false));
} else if (intentName === 'AMAZON.StopIntent' || intentName === 'AMAZON.CancelIntent') {
handleSessionEndRequest(callback);
} else {
throw new Error('Invalid intent');
}
}
/**
* Called when the user ends the session.
* Is not called when the skill returns shouldEndSession=true.
*/
function onSessionEnded(sessionEndedRequest, session) {
console.log(`onSessionEnded requestId=${sessionEndedRequest.requestId}, sessionId=${session.sessionId}`);
// Add cleanup logic here
}
// --------------- Main handler -----------------------
// Route the incoming request based on type (LaunchRequest, IntentRequest,
// etc.) The JSON body of the request is provided in the event parameter.
exports.handler = (event, context, callback) => {
try {
console.log(`event.session.application.applicationId=${event.session.application.applicationId}`);
if (event.session.application.applicationId !== 'amzn1.ask.skill.b0ebfcb9-9323-4470-914f-d3e001a3e0ba') {
callback('Invalid Application ID');
}
if (event.session.new) {
onSessionStarted({ requestId: event.request.requestId }, event.session);
}
if (event.request.type === 'LaunchRequest') {
onLaunch(event.request,
event.session,
(sessionAttributes, speechletResponse) => {
callback(null, buildResponse(sessionAttributes, speechletResponse));
});
} else if (event.request.type === 'IntentRequest') {
onIntent(event.request,
event.session,
(sessionAttributes, speechletResponse) => {
callback(null, buildResponse(sessionAttributes, speechletResponse));
});
} else if (event.request.type === 'SessionEndedRequest') {
onSessionEnded(event.request, event.session);
callback();
}
} catch (err) {
callback(err);
}
};
Comments