connect.js
File `connect.js` has 490 lines of code (exceeds 250 allowed). Consider refactoring.const alexa = require('alexa-app');const request = require('request-promise-native');const express = require('express');const nodecache = require('node-cache');const i18n = require('i18n'); const rq = require('./request-helper');const { findDeviceByName, requestDevices } = require('./device-helper'); // Create instance of expressvar express_app = express();// Create a 1 hour cache for storing user devicesvar cache = new nodecache({ stdTTL: 3600, checkperiod: 120 });// Create instance of alexa-appvar app = new alexa.app('connect');// Bind alexa-app to express instanceapp.express({ expressApp: express_app }); const successSound = "<audio src='soundbank://soundlibrary/ui/gameshow/amzn_ui_sfx_gameshow_neutral_response_02'/>";const connectDeviceCard = (context) => ({ type: "Simple", title: context.__("Connecting to a device using Spotify Connect"), content: context.__("To add a device to Spotify Connect," + " log in to your Spotify account on a supported device" + " such as an Echo, phone, or computer" + "\nhttps://support.spotify.com/uk/article/spotify-connect/")});const applicationId = require('./package.json').alexa.applicationId; // Run every time the skill is accessedapp.pre = function (req, res, _type) { i18n.configure({ directory: __dirname + '/locales', defaultLocale: 'en-GB', register: req.context, }); if (req.data.request.locale) { req.context.setLocale(req.data.request.locale); } // Error if the application ID of the request is not for this skill if (req.applicationId != applicationId && req.getSession().details.application.applicationId != applicationId) { throw "Invalid applicationId"; } // Check that the user has an access token, if they have linked their account if (!(req.context.System.user.accessToken || req.getSession().details.user.accessToken)) { res.say(req.context.__("You have not linked your Spotify account, check your Alexa app to link the account")); res.linkAccount(); }}; // Run after every requestapp.post = function (req, res, _type, exception) { if (exception) { return res.clear().say(req.context.__("An error occured: ") + exception).send(); }}; // Function for when skill is invoked without intentapp.launch(function (req, res) { res.say(req.context.__("I can control your Spotify Connect devices, to start, ask me to list your devices")) .reprompt(req.context.__("To start, ask me to list your devices")); // Keep session open res.shouldEndSession(false);}); // Handle default Amazon help intent// No slots or utterances requiredapp.intent("AMAZON.HelpIntent", { "slots": {}, "utterances": []}, function (req, res) { res.say(req.context.__("You can ask me to list your connect devices and then control them. ")) .say(req.context.__("For example, tell me to play on a device after listing devices")) .reprompt(req.context.__("What would you like to do?")); // Keep session open res.shouldEndSession(false);}); // Handle default Amazon stop intent// No slots or utterances requiredapp.intent("AMAZON.StopIntent", { "slots": {}, "utterances": []}, function (_req, _res) { return;}); // Handle default Amazon cancel intent// No slots or utterances requiredapp.intent("AMAZON.CancelIntent", { "slots": {}, "utterances": []}, function (_req, _res) { return;}); // Handle play intent// No slots requiredapp.intent('PlayIntent', { "utterances": [ "play", "resume", "continue" ]}, function (req, res) { // PUT to Spotify REST API return rq.put("https://api.spotify.com/v1/me/player/play", req.getSession().details.user.accessToken) .then((r) => { req.getSession().set("statusCode", r.statusCode); res.say(successSound); }).catch((err) => { if (err.statusCode === 403) res.say(req.context.__("Make sure your Spotify account is premium")); if (err.statusCode === 404) { res.say(req.context.__("I couldn't find any connect devices, check your Alexa app for information on connecting a device")); res.card(connectDeviceCard(req.context)); } }); }); // Handle pause intent// No slots requiredapp.intent('PauseIntent', { "utterances": [ "pause" ]},Similar blocks of code found in 3 locations. Consider refactoring. function (req, res) { // PUT to Spotify REST API return rq.put("https://api.spotify.com/v1/me/player/pause", req.getSession().details.user.accessToken) .then((r) => { req.getSession().set("statusCode", r.statusCode); res.say(successSound); }).catch((err) => { if (err.statusCode === 403) res.say(req.context.__("Make sure your Spotify account is premium")); if (err.statusCode === 404) { res.say(req.context.__("I couldn't find any connect devices, check your Alexa app for information on connecting a device")); res.card(connectDeviceCard(req)); } }); }); // Handle skip next intent// No slots requiredapp.intent('SkipNextIntent', { "utterances": [ "skip", "next", "forwards" ]},Similar blocks of code found in 3 locations. Consider refactoring. function (req, res) { // POST to Spotify REST API return rq.post("https://api.spotify.com/v1/me/player/next", req.getSession().details.user.accessToken) .then((r) => { req.getSession().set("statusCode", r.statusCode); res.say(successSound); }).catch((err) => { if (err.statusCode === 403) res.say(req.context.__("Make sure your Spotify account is premium")); if (err.statusCode === 404) { res.say(req.context.__("I couldn't find any connect devices, check your Alexa app for information on connecting a device")); res.card(connectDeviceCard(res)); } }); }); // Handle skip previous intent// No slots requiredapp.intent('SkipPreviousIntent', { "utterances": [ "previous", "last", "back", "backwards" ]},Similar blocks of code found in 3 locations. Consider refactoring. function (req, res) { // POST to Spotify REST API return rq.post("https://api.spotify.com/v1/me/player/previous", req.getSession().details.user.accessToken) .then((r) => { req.getSession().set("statusCode", r.statusCode); res.say(successSound); }).catch((err) => { if (err.statusCode === 403) res.say(req.context.__("Make sure your Spotify account is premium")); if (err.statusCode === 404) { res.say(req.context.__("I couldn't find any connect devices, check your Alexa app for information on connecting a device")); res.card(connectDeviceCard(res)); } }); }); // PUT to Spotify REST APIconst setVolume = (volumePercent, req, res) => { return request.put({ // Send new volume * 10 (convert to percentage) url: "https://api.spotify.com/v1/me/player/volume?volume_percent=" + volumePercent, // Send access token as bearer auth auth: { "bearer": req.getSession().details.user.accessToken }, // Handle sending as JSON json: true }).catch((err) => { if (err.statusCode === 403) res.say(req.context.__("Make sure your Spotify account is premium")); if (err.statusCode === 404) { res.say(req.context.__("I couldn't find any connect devices, check your Alexa app for information on connecting a device")); res.card(connectDeviceCard(res)); } });}; Function `getAndValidateVolumePercentFromSlot` has a Cognitive Complexity of 28 (exceeds 5 allowed). Consider refactoring.
Function `getAndValidateVolumePercentFromSlot` has 33 lines of code (exceeds 25 allowed). Consider refactoring.const getAndValidateVolumePercentFromSlot = (req, res, isPercentIntent) => { const slotName = isPercentIntent ? "VOLUMEPERCENT" : "VOLUMELEVEL" // Check that the slot has a value if (req.slot(slotName)) { // Check if the slot is a number if (!isNaN(req.slot(slotName))) { var volumeValue = req.slot(slotName); // Check that the volume is valid if (volumeValue >= 0 && volumeValue <= (isPercentIntent ? 100 : 10)) { return (isPercentIntent ? 1 : 10) * volumeValue; } else { // If not valid volume res.say(req.context.__(isPercentIntent ? "You can only set the volume percent between 0 and 100" : "You can only set the volume between 0 and 10")); // Keep session open res.shouldEndSession(false); return null; } } else { // Not a number res.say(req.context.__(isPercentIntent ? "Try setting a volume percent between 0 and 100" : "Try setting a volume between 0 and 10")) .reprompt(req.context.__("What would you like to do?")); // Keep session open res.shouldEndSession(false); return null; } } else { // No slot value res.say(req.context.__("I couldn't work out the volume to use.")) .say(req.context.__(isPercentIntent ? "Try setting a volume percent between 0 and 100" : "Try setting a volume between 0 and 10")) .reprompt(req.context.__("What would you like to do?")); // Keep session open res.shouldEndSession(false); return null; }}; // Handle 0-10 volume level intent// Slot for new volumeSimilar blocks of code found in 2 locations. Consider refactoring.app.intent('VolumeLevelIntent', { "slots": { "VOLUMELEVEL": "AMAZON.NUMBER" }, "utterances": [ "{set the|set|} volume {level|} {to|} {-|VOLUMELEVEL}" ]}, function (req, res) { // Check that request contains session if (req.hasSession()) { const volumePercent = getAndValidateVolumePercentFromSlot(req, res, false); if (volumePercent !== null) { return setVolume(volumePercent, req, res); } } }); // Handle volume percent intent// Slot for new volumeSimilar blocks of code found in 2 locations. Consider refactoring.app.intent('VolumePercentIntent', { "slots": { "VOLUMEPERCENT": "AMAZON.NUMBER" }, "utterances": [ "{set the|set|} volume {level|} {to|} {-|VOLUMEPERCENT} percent" ]}, function (req, res) { // Check that request contains session if (req.hasSession()) { const volumePercent = getAndValidateVolumePercentFromSlot(req, res, true); if (volumePercent !== null) { return setVolume(volumePercent, req, res); } } }); // Handle get devices intent// No slots requiredapp.intent('GetDevicesIntent', { "utterances": [ "devices", "list", "search", "find" ]}, function (req, res) { // GET from Spotify REST API return requestDevices(req, cache) .then(function (devices) { var deviceNames = devices.map(d => d.name); // Check if user has devices if (devices.length > 0) { // Comma separated list of device names res.say(req.context.__("I found these connect devices: ")) .say([deviceNames.slice(0, -1).join(', '), deviceNames.slice(-1)[0]].join(deviceNames.length < 2 ? '' : ',' + req.context.__(' and ')) + ". ") .say(req.context.__("What would you like to do with these devices?")).reprompt(req.context.__("What would you like to do?")); // Keep session open res.shouldEndSession(false); } else { // No devices found res.say(req.context.__("I couldn't find any connect devices, check your Alexa app for information on connecting a device")); res.card(connectDeviceCard(res)); } }) // Handle errors .catch(function (err) { req.getSession().set("statusCode", err.statusCode); }); }); // Handle device play intent// Slot for device nameapp.intent('DevicePlayIntent', { "slots": { "DEVICE": "AMAZON.SearchQuery" }, "utterances": [ "play on {my|device|} {-|DEVICE}" ]}, function (req, res) { // Check that request contains session if (req.hasSession()) { // Check that the slot has a value var DEVICE = req.slot("DEVICE");Similar blocks of code found in 2 locations. Consider refactoring. if (DEVICE) { return findDeviceByName(req, cache, DEVICE).then(async (device) => { // Check that the device was foundIdentical blocks of code found in 2 locations. Consider refactoring. if (device.id) { // PUT to Spotify REST API await request.put({ url: "https://api.spotify.com/v1/me/player", // Send access token as bearer auth auth: { "bearer": req.getSession().details.user.accessToken }, body: { // Send device ID "device_ids": [ device.id ], // Make sure that music plays "play": true }, // Handle sending as JSON json: true }).then((_r) => { res.say(req.context.__("Playing on device {{deviceName}}", { deviceName: device.name })); }).catch((err) => { if (err.statusCode === 403) res.say(req.context.__("Make sure your Spotify account is premium")); if (err.statusCode === 404) { res.say(req.context.__("I couldn't find any connect devices, check your Alexa app for information on connecting a device")); res.card(connectDeviceCard(res)); } }); } else { // If device not found res.say(req.context.__("I couldn't find a device named {{DEVICE}}.", { DEVICE })) .say(req.context.__("Try asking me to list devices first")); // Keep session open res.shouldEndSession(false); } }); } else { // No slot value res.say(req.context.__("I couldn't work out which device to play on.")) .say(req.context.__("Try asking me to list devices first")) .reprompt(req.context.__("What would you like to do?")); // Keep session open res.shouldEndSession(false); } } }); // Handle track queue intent// Slot for track nameapp.intent( "QueueTrackIntent", { slots: { TRACKNAME: "AMAZON.MusicRecording", }, utterances: [ "queue {the song|} {-|TRACKNAME}", "add {the song|} {-|TRACKNAME} to {my|the} queue", ], }, function (req, res) { // Check that request contains session if (!req.hasSession()) { return; } // Check that the slot has a value if (!req.slot("TRACKNAME")) { // No slot value res .say(req.context.__("I couldn't work out which song you want to queue.")) .reprompt(req.context.__("What would you like to do?")); // Keep session open res.shouldEndSession(false); } var trackSearchQuery = req.slot("TRACKNAME");Identical blocks of code found in 2 locations. Consider refactoring. return request.get({ url: `https://api.spotify.com/v1/search`, // Send access token as bearer auth auth: { bearer: req.getSession().details.user.accessToken, }, qs: { q: trackSearchQuery, type: "track", offset: "0", }, // Handle sending as JSON json: true, }) .then((body) => { if (!body.tracks || !body.tracks.items[0] || !body.tracks.items[0].uri || !body.tracks.items[0].name) { throw "bad response"; } var trackId = body.tracks.items[0].uri; var trackName = body.tracks.items[0].name; Identical blocks of code found in 2 locations. Consider refactoring. return request.post({ url: "https://api.spotify.com/v1/me/player/queue", auth: { bearer: req.getSession().details.user.accessToken, }, qs: { // Send track ID uri: trackId, }, // Handle sending as JSON json: true, }) .then((response) => { res.say( req.context.__("Queued track {{trackName}}", { trackName, }) ); }) .catch((err) => { res .say(req.context.__("Sorry, I couldn't queue that song.")) .reprompt(req.context.__("What would you like to do?")); }); }) .catch((err) => { res .say(req.context.__("Sorry, I couldn't queue that song.")) .reprompt(req.context.__("What would you like to do?")); }); });// Handle device transfer intent// Slot for device nameapp.intent('DeviceTransferIntent', { "slots": { "DEVICE": "AMAZON.SearchQuery" }, "utterances": [ "{transfer|switch|swap|move} to {my|device|} {-|DEVICE}", "use {my|device|} {-|DEVICE}" ]}, function (req, res) { // Check that request contains session if (req.hasSession()) { // Check that the slot has a value var DEVICE = req.slot("DEVICE");Similar blocks of code found in 2 locations. Consider refactoring. if (DEVICE) { return findDeviceByName(req, cache, DEVICE).then(async (device) => { // Check that the device was foundIdentical blocks of code found in 2 locations. Consider refactoring. if (device.id) { // PUT to Spotify REST API await request.put({ url: "https://api.spotify.com/v1/me/player", // Send access token as bearer auth auth: { "bearer": req.getSession().details.user.accessToken }, body: { // Send device ID "device_ids": [ device.id ] }, // Handle sending as JSON json: true }).then((_r) => { res.say(req.context.__("Transferring to {{deviceName}}", {deviceName: device.name })); }).catch((err) => { if (err.statusCode === 403) res.say(req.context.__("Make sure your Spotify account is premium")); if (err.statusCode === 404) { res.say(req.context.__("I couldn't find any connect devices, check your Alexa app for information on connecting a device")); res.card(connectDeviceCard(res)); } }); } else { // If device not found res.say(req.context.__("I couldn't find a device named {{DEVICE}}.", { DEVICE })) .say(req.context.__("Try asking me to list devices first")); // Keep session open res.shouldEndSession(false); } }); } else { // No slot value res.say(req.context.__("I couldn't work out which device to transfer to.")) .say(req.context.__("Try asking me to list devices first")) .reprompt(req.context.__("What would you like to do?")); // Keep session open res.shouldEndSession(false); } } }); // Handle get track intent// No slots requiredapp.intent('GetTrackIntent', { "utterances": [ "{what is|what's} {playing|this song}", "what {song|track|} is this" ]}, function (req, res) { // GET from Spotify REST APISimilar blocks of code found in 2 locations. Consider refactoring. return request.get({ url: "https://api.spotify.com/v1/me/player/currently-playing", // Send access token as bearer auth auth: { "bearer": req.getSession().details.user.accessToken }, // Parse results as JSON json: true }) .then(function (body) { if (body.is_playing) {Similar blocks of code found in 2 locations. Consider refactoring. res.say(req.context.__("This is {{name}} by {{artist}}", { name: body.item.name, artist: body.item.artists[0].name })); } else { if (body.item.name) { // If not playing but last track knownSimilar blocks of code found in 2 locations. Consider refactoring. res.say(req.context.__("That was {{name}} by {{artist}}", { name: body.item.name, artist: body.item.artists[0].name })); } else { // If unknown res.say(req.context.__("Nothing is playing")); } } }) // Handle errors .catch(function (err) { req.getSession().set("statusCode", err.statusCode); }); }); // Set up redirect to project pageexpress_app.use(express.static(__dirname)); /* istanbul ignore next */express_app.get('/', function (_, res) { res.redirect('https://github.com/thorpelawrence/alexa-spotify-connect');}); /* istanbul ignore if */// Only listen if run directly, not if required as a moduleif (require.main === module) { var port = process.env.PORT || 8888; console.log("Listening on port " + port); express_app.listen(port);} /* istanbul ignore next */express_app.get('/skill', function (_, res) { const skill = require('./skill/skill.js'); skill.forEach(locale => { res.write(locale.name + '\n'); res.write(JSON.stringify(locale.data, null, 2) + '\n'); res.write('='.repeat(80) + '\n'); }); res.end();}); // Export alexa-app instance for skill.jsmodule.exports = app;