From e23a6aa55692274114116b308d0db562e69d09a7 Mon Sep 17 00:00:00 2001 From: Jimmy Shimizu Date: Mon, 28 Nov 2016 22:55:14 +0100 Subject: [PATCH 001/123] Should switch to queue if invoking spotify/now. Fixes #390 Switch to queue playback if not already in queue mode Seek to firsttracknumberenqueued instead of nextTrack to handle discrepancy in desired and actual queue track number --- lib/actions/spotify.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/lib/actions/spotify.js b/lib/actions/spotify.js index c57702d6..dbbd8c5a 100644 --- a/lib/actions/spotify.js +++ b/lib/actions/spotify.js @@ -28,9 +28,17 @@ function spotify(player, values) { return player.coordinator.addURIToQueue(uri, metadata); } else if (action == 'now') { var nextTrackNo = player.coordinator.state.trackNo + 1; - return player.coordinator.addURIToQueue(uri, metadata, true, nextTrackNo) - .then(() => player.coordinator.nextTrack()) - .then(() => player.coordinator.play()) + let promise = Promise.resolve(); + if (player.coordinator.avTransportUri.startsWith('x-rincon-queue') === false) { + promise = promise.then(() => player.coordinator.setAVTransport(`x-rincon-queue:${player.coordinator.uuid}#0`)); + } + + return promise.then(() => { + return player.coordinator.addURIToQueue(uri, metadata, true, nextTrackNo) + .then((addToQueueStatus) => player.coordinator.trackSeek(addToQueueStatus.firsttracknumberenqueued)) + .then(() => player.coordinator.play()); + }); + } else if (action == 'next') { var nextTrackNo = player.coordinator.state.trackNo + 1; return player.coordinator.addURIToQueue(uri, metadata, true, nextTrackNo); From 1f8fd28c3766128e81453e0b213fc3ccba9a1a62 Mon Sep 17 00:00:00 2001 From: Jimmy Shimizu Date: Sun, 4 Dec 2016 22:42:53 +0100 Subject: [PATCH 002/123] Now support for multiple TTS solutions Introduce support for Microsoft Cognitive Services Define household ID in settings to map installation to specific Sonos installation in case of multiple ones --- README.md | 47 ++++++++++++++- lib/actions/say.js | 2 +- lib/helpers/try-download-tts.js | 57 +++++------------- lib/tts-providers/microsoft.js | 102 ++++++++++++++++++++++++++++++++ lib/tts-providers/voicerss.js | 49 +++++++++++++++ package.json | 4 +- server.js | 4 +- 7 files changed, 218 insertions(+), 47 deletions(-) create mode 100644 lib/tts-providers/microsoft.js create mode 100644 lib/tts-providers/voicerss.js diff --git a/README.md b/README.md index 5fe6168a..c2449bc2 100644 --- a/README.md +++ b/README.md @@ -303,6 +303,12 @@ Example: ```json { "voicerss": "Your api key for TTS with voicerss", + "microsoft": { + "key": "Your api for Bing speech API", + "gender": "Female", + "name": "Microsoft Server Speech Text to Speech Voice (en-US, ZiraRUS)", + "language": "en-US" + }, "port": 5005, "securePort": 5006, "https": { @@ -354,7 +360,16 @@ and it will replace the queue with the playlist and starts playing. Say (TTS support) ----------------- -Experimental support for TTS. This REQUIRES a registered API key from voiceRSS! See http://www.voicerss.org/ for info. +Experimental support for TTS. Today the following providers are available: + +* voicerss +* Microsoft Cognitive Services (Bing Text to Speech API) + +It will use the one you configure in settings.json. If you define settings for multiple TTS services, it will not be guaranteed which one it will choose! + +#### VoiceRSS + +This REQUIRES a registered API key from voiceRSS! See http://www.voicerss.org/ for info. You need to add this to a file called settings.json (create if it doesn't exist), like this: @@ -414,6 +429,36 @@ The supported language codes are: | es-es | Spanish (Spain) | | sv-se | Swedish (Sweden) | +#### Microsoft +This one also requires a registered api key. You can sign up for free here: https://www.microsoft.com/cognitive-services/en-US/subscriptions?mode=NewTrials and select "Bing Speech - Preview". + +The following configuration is available (the entered values except key are default, and may be omitted): + +```json + { + "microsoft": { + "key": "Your api for Bing speech API", + "gender": "Female", + "name": "Microsoft Server Speech Text to Speech Voice (en-US, ZiraRUS)", + "language": "en-US" + } + } +``` + +If you change language, you need to change the name matching the gender for that language, according to this list: https://www.microsoft.com/cognitive-services/en-us/speech-api/documentation/API-Reference-REST/BingVoiceOutput#SupLocales. This one doesn't support providing language directly in the request for this reason. + +Action is: + + /[Room name]/say/[phrase][/[announce volume]] + /sayall/[phrase][/[announce volume]] + +Example: + + /Office/say/Hello, dinner is ready + /sayall/Hello, dinner is ready + /Office/say/Hello, dinner is ready/90 + + Line-in ------- diff --git a/lib/actions/say.js b/lib/actions/say.js index d22eebb6..71aa9ef7 100644 --- a/lib/actions/say.js +++ b/lib/actions/say.js @@ -11,7 +11,7 @@ let port; let system; function say(player, values) { - const text = values[0]; + const text = decodeURIComponent(values[0]); let announceVolume; let language; diff --git a/lib/helpers/try-download-tts.js b/lib/helpers/try-download-tts.js index 2f308d82..dc97c209 100644 --- a/lib/helpers/try-download-tts.js +++ b/lib/helpers/try-download-tts.js @@ -1,49 +1,24 @@ 'use strict'; -const crypto = require('crypto'); -const fs = require('fs'); -const http = require('http'); const path = require('path'); -const settings = require('../../settings'); +const requireDir = require('sonos-discovery/lib/helpers/require-dir'); +const providers = []; -function tryDownloadTTS(phrase, language) { - if (!settings.voicerss) { - console.error('You need to register an apikey at http://www.voicerss.org and add it to settings.json!'); - return Promise.resolve(`/missing_api_key.mp3`); - - } - // Use voicerss tts translation service to create a mp3 file - const ttsRequestUrl = `http://api.voicerss.org/?key=${settings.voicerss}&f=22khz_16bit_mono&hl=${language}&src=${phrase}`; - - // Construct a filesystem neutral filename - const filename = crypto.createHash('sha1').update(phrase).digest('hex') + '-' + language + '.mp3'; - const filepath = path.resolve(settings.webroot, 'tts', filename); +requireDir(path.join(__dirname, '../tts-providers'), (provider) => { + providers.push(provider); +}); - const expectedUri = `/tts/${filename}`; - try { - fs.accessSync(filepath, fs.R_OK); - return Promise.resolve(expectedUri); - } catch (err) { - console.log(`announce file for phrase "${phrase}" does not seem to exist, downloading`); - } - - return new Promise((resolve, reject) => { - var file = fs.createWriteStream(filepath); - http.get(ttsRequestUrl, function (response) { - if (response.statusCode < 300 && response.statusCode >= 200) { - response.pipe(file); - file.on('finish', function () { - file.end(); - resolve(expectedUri); +function tryDownloadTTS(phrase, language) { + let path; + return providers.reduce((promise, provider) => { + return promise.then(() => { + if (path) return path; + return provider(phrase, language) + .then((_path) => { + path = _path; + return path; }); - } else { - reject(new Error(`Download failed with status ${response.statusCode}, ${response.message}`)); - - } - }).on('error', function (err) { - fs.unlink(dest); - reject(err); - }); - }); + }); + }, Promise.resolve()); } module.exports = tryDownloadTTS; \ No newline at end of file diff --git a/lib/tts-providers/microsoft.js b/lib/tts-providers/microsoft.js new file mode 100644 index 00000000..a48d615b --- /dev/null +++ b/lib/tts-providers/microsoft.js @@ -0,0 +1,102 @@ +'use strict'; +const crypto = require('crypto'); +const fs = require('fs'); +const request = require('sonos-discovery/lib/helpers/request'); +const logger = require('sonos-discovery/lib/helpers/logger'); +const path = require('path'); +const globalSettings = require('../../settings'); + +const APP_ID = '9aa44d9e6ec14da99231a9166fd50b0f'; +const INSTANCE_ID = crypto.randomBytes(16).toString('hex'); +const TOKEN_EXPIRATION = 590000; // 9:50 minutes in ms +const DEFAULT_SETTINGS = { + language: 'en-US', + gender: 'Female', + name: 'Microsoft Server Speech Text to Speech Voice (en-US, ZiraRUS)' +}; + +let bearerToken; +let bearerExpires = Date.now(); + +function generateBearerToken(apiKey) { + return request({ + uri: 'https://api.cognitive.microsoft.com/sts/v1.0/issueToken', + method: 'POST', + type: 'raw', + headers: { + 'Ocp-Apim-Subscription-Key': apiKey, + 'Content-Length': 0 + } + }) + .then((body) => { + logger.debug(`Bearer token: body`); + bearerToken = body; + bearerExpires = Date.now() + TOKEN_EXPIRATION; + }); +} + +function format(lang, gender, name, text) { + return `${text}`; +} + +function microsoft(phrase, language) { + if (!globalSettings.microsoft || !globalSettings.microsoft.key) { + return Promise.resolve(); + } + + const settings = Object.assign({}, DEFAULT_SETTINGS, globalSettings.microsoft); + + const phraseHash = crypto.createHash('sha1').update(phrase).digest('hex'); + const filename = `microsoft-${phraseHash}-${settings.language}-${settings.gender}.wav`; + const filepath = path.resolve(globalSettings.webroot, 'tts', filename); + + const expectedUri = `/tts/${filename}`; + try { + fs.accessSync(filepath, fs.R_OK); + return Promise.resolve(expectedUri); + } catch (err) { + logger.info(`announce file for phrase "${phrase}" does not seem to exist, downloading`); + } + + let promise = Promise.resolve(); + if (bearerExpires < Date.now()) { + // Refresh token + promise = generateBearerToken(settings.key); + } + + return promise.then(() => { + const ssml = format(settings.language, settings.gender, settings.name, phrase); + return request({ + uri: 'https://speech.platform.bing.com/synthesize', + method: 'POST', + type: 'stream', + headers: { + Authorization: `Bearer ${bearerToken}`, + 'Content-Type': 'application/ssml+xml', + 'X-Microsoft-OutputFormat': 'riff-16khz-16bit-mono-pcm', + 'X-Search-AppId': APP_ID, + 'X-Search-ClientID': INSTANCE_ID, + 'User-Agent': 'node-sonos-http-api' + }, + body: ssml + }) + .then(res => { + return new Promise((resolve) => { + + const file = fs.createWriteStream(filepath); + res.pipe(file); + + res.on('end', () => { + resolve(expectedUri); + }) + }) + + }) + .catch((err) => { + logger.error(err); + throw err; + }); + }); +} + +module.exports = microsoft; \ No newline at end of file diff --git a/lib/tts-providers/voicerss.js b/lib/tts-providers/voicerss.js new file mode 100644 index 00000000..4d6786a9 --- /dev/null +++ b/lib/tts-providers/voicerss.js @@ -0,0 +1,49 @@ +'use strict'; +const crypto = require('crypto'); +const fs = require('fs'); +const http = require('http'); +const path = require('path'); +const settings = require('../../settings'); + +function voicerss(phrase, language) { + if (!settings.voicerss) { + return Promise.resolve(); + + } + // Use voicerss tts translation service to create a mp3 file + const ttsRequestUrl = `http://api.voicerss.org/?key=${settings.voicerss}&f=22khz_16bit_mono&hl=${language}&src=${encodeURIComponent(phrase)}`; + + // Construct a filesystem neutral filename + const phraseHash = crypto.createHash('sha1').update(phrase).digest('hex'); + const filename = `voicerss-${phraseHash}-${language}.mp3`; + const filepath = path.resolve(settings.webroot, 'tts', filename); + + const expectedUri = `/tts/${filename}`; + try { + fs.accessSync(filepath, fs.R_OK); + return Promise.resolve(expectedUri); + } catch (err) { + console.log(`announce file for phrase "${phrase}" does not seem to exist, downloading`); + } + + return new Promise((resolve, reject) => { + var file = fs.createWriteStream(filepath); + http.get(ttsRequestUrl, function (response) { + if (response.statusCode < 300 && response.statusCode >= 200) { + response.pipe(file); + file.on('finish', function () { + file.end(); + resolve(expectedUri); + }); + } else { + reject(new Error(`Download from voicerss failed with status ${response.statusCode}, ${response.message}`)); + + } + }).on('error', function (err) { + fs.unlink(dest); + reject(err); + }); + }); +} + +module.exports = voicerss; \ No newline at end of file diff --git a/package.json b/package.json index 3f521f43..c879639f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sonos-http-api", - "version": "1.2.1", + "version": "1.2.2", "description": "A simple node app for controlling a Sonos system with basic HTTP requests", "scripts": { "start": "node server.js" @@ -16,7 +16,7 @@ "anesidora": "^1.2.0", "node-static": "~0.7.0", "request-promise": "~1.0.2", - "sonos-discovery": "https://github.com/jishi/node-sonos-discovery/archive/v1.2.1.tar.gz" + "sonos-discovery": "https://github.com/jishi/node-sonos-discovery/archive/v1.2.2.tar.gz" }, "engines": { "node": ">=4.0.0", diff --git a/server.js b/server.js index ca427e00..a5700431 100644 --- a/server.js +++ b/server.js @@ -3,14 +3,14 @@ const http = require('http'); const https = require('https'); const fs = require('fs'); const auth = require('basic-auth'); -const SonosDiscovery = require('sonos-discovery'); +const SonosSystem = require('sonos-discovery'); const logger = require('sonos-discovery/lib/helpers/logger'); const SonosHttpAPI = require('./lib/sonos-http-api.js'); const nodeStatic = require('node-static'); const settings = require('./settings'); const fileServer = new nodeStatic.Server(settings.webroot); -const discovery = new SonosDiscovery(settings); +const discovery = new SonosSystem(settings); const api = new SonosHttpAPI(discovery, settings); var requestHandler = function (req, res) { From bd81ceb76172c9bca8c69f6e0724c3a926e33e21 Mon Sep 17 00:00:00 2001 From: Jimmy Shimizu Date: Mon, 5 Dec 2016 21:15:49 +0100 Subject: [PATCH 003/123] Add AWS Polly support Experimental support for Polly --- README.md | 81 ++++++++++++++++++++++++++++++++++ lib/tts-providers/aws-polly.js | 51 +++++++++++++++++++++ package.json | 5 ++- 3 files changed, 135 insertions(+), 2 deletions(-) create mode 100644 lib/tts-providers/aws-polly.js diff --git a/README.md b/README.md index c2449bc2..6cbd9257 100644 --- a/README.md +++ b/README.md @@ -364,6 +364,7 @@ Experimental support for TTS. Today the following providers are available: * voicerss * Microsoft Cognitive Services (Bing Text to Speech API) +* AWS Polly It will use the one you configure in settings.json. If you define settings for multiple TTS services, it will not be guaranteed which one it will choose! @@ -458,6 +459,86 @@ Example: /sayall/Hello, dinner is ready /Office/say/Hello, dinner is ready/90 +#### AWS Polly + +Requires AWS access tokens, which you generate for your user. Since this uses the AWS SDK, it will look for settings in either Environment variables, the ~/.aws/credentials or ~/.aws/config. + +You can also specify it for this application only, using: +```json + { + "aws": { + "credentials": { + "region": "eu-west-1", + "accessKeyId": "Your access key id", + "secretAccessKey": "Your secret" + }, + "name": "Joanna" + } + } +``` + +Choose the region where you registered your account, or the one closest to you. Polly is only supported in US East (Northern Virginia), US West (Oregon), US East (Ohio), and EU (Ireland) as of today (dec 2016) + +If you have your credentials elsewhere and want to stick with the default voice, you still need to make sure that the aws config option is set to trigger AWS TTS: + +```json + { + "aws": {} + } +``` + +This is the current list of voice names and their corresponding language and accent + +| Language | Code | Gender | Name | +| --------- | ---- | ------ | ---- | +| Australian English | en-AU | Female | Nicole | +| Australian English | en-AU | Male | Russell | +| Brazilian Portuguese | pt-BR | Female | Vitoria | +| Brazilian Portuguese | pt-BR | Male | Ricardo | +| British English | en-GB | Male | Brian | +| British English | en-GB | Female | Emma | +| British English | en-GB | Female | Amy | +| Canadian French | fr-CA | Female | Chantal | +| Castilian Spanish | es-ES | Female | Conchita | +| Castilian Spanish | es-ES | Male | Enrique | +| Danish | da-DK | Female | Naja | +| Danish | da-DK | Male | Mads | +| Dutch | nl-NL | Male | Ruben | +| Dutch | nl-NL | Female | Lotte | +| French | fr-FR | Male | Mathieu | +| French | fr-FR | Female | Celine | +| German | de-DE | Female | Marlene | +| German | de-DE | Male | Hans | +| Icelandic | is-IS | Male | Karl | +| Icelandic | is-IS | Female | Dora | +| Indian English | en-IN | Female | Raveena | +| Italian | it-IT | Female | Carla | +| Italian | it-IT | Male | Giorgio | +| Japanese | ja-JP | Female | Mizuki | +| Norwegian | nb-NO | Female | Liv | +| Polish | pl-PL | Female | Maja | +| Polish | pl-PL | Male | Jacek | +| Polish | pl-PL | Male | Jan | +| Polish | pl-PL | Female | Ewa | +| Portuguese | pt-PT | Female | Ines | +| Portuguese | pt-PT | Male | Cristiano | +| Romanian | ro-RO | Female | Carmen | +| Russian | ru-RU | Female | Tatyana | +| Russian | ru-RU | Male | Maxim | +| Swedish | sv-SE | Female | Astrid | +| Turkish | tr-TR | Female | Filiz | +| US English | en-US | Male | Justin | +| US English | en-US | Female | Joanna | +| US English | en-US | Male | Joey | +| US English | en-US | Female | Ivy | +| US English | en-US | Female | Salli | +| US English | en-US | Female | Kendra | +| US English | en-US | Female | Kimberly | +| US Spanish | es-US | Female | Penelope | +| US Spanish | es-US | Male | Miguel | +| Welsh | cy-GB | Female | Gwyneth | +| Welsh | English | en-GB-WLS | Male | Geraint | + Line-in ------- diff --git a/lib/tts-providers/aws-polly.js b/lib/tts-providers/aws-polly.js new file mode 100644 index 00000000..ef69690f --- /dev/null +++ b/lib/tts-providers/aws-polly.js @@ -0,0 +1,51 @@ +'use strict'; +const crypto = require('crypto'); +const fs = require('fs'); +const http = require('http'); +const path = require('path'); +const AWS = require('aws-sdk'); +const settings = require('../../settings'); + +const DEFAULT_SETTINGS = { + OutputFormat: 'mp3', + VoiceId: 'Joanna', + TextType: 'text' +}; + +function polly(phrase, language) { + if (!settings.aws) { + return Promise.resolve(); + + } + + // Construct a filesystem neutral filename + const dynamicParameters = { Text: phrase }; + if (settings.aws.name) { + dynamicParameters.VoiceId = settings.aws.name; + } + const synthesizeParameters = Object.assign({}, DEFAULT_SETTINGS, dynamicParameters); + const phraseHash = crypto.createHash('sha1').update(phrase).digest('hex'); + const filename = `polly-${phraseHash}-${synthesizeParameters.VoiceId}.mp3`; + const filepath = path.resolve(settings.webroot, 'tts', filename); + + const expectedUri = `/tts/${filename}`; + try { + fs.accessSync(filepath, fs.R_OK); + return Promise.resolve(expectedUri); + } catch (err) { + console.log(`announce file for phrase "${phrase}" does not seem to exist, downloading`); + } + + const constructorParameters = Object.assign({ apiVersion: '2016-06-10' }, settings.aws.credentials); + + const polly = new AWS.Polly(constructorParameters); + + return polly.synthesizeSpeech(synthesizeParameters) + .promise() + .then((data) => { + fs.writeFileSync(filepath, data.AudioStream); + return expectedUri; + }); +} + +module.exports = polly; \ No newline at end of file diff --git a/package.json b/package.json index c879639f..c6547caf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sonos-http-api", - "version": "1.2.2", + "version": "1.2.3", "description": "A simple node app for controlling a Sonos system with basic HTTP requests", "scripts": { "start": "node server.js" @@ -11,9 +11,10 @@ "url": "https://github.com/jishi/node-sonos-http-api.git" }, "dependencies": { + "anesidora": "^1.2.0", + "aws-sdk": "^2.7.10", "basic-auth": "~1.0.3", "fuse.js": "^2.2.0", - "anesidora": "^1.2.0", "node-static": "~0.7.0", "request-promise": "~1.0.2", "sonos-discovery": "https://github.com/jishi/node-sonos-discovery/archive/v1.2.2.tar.gz" From 9c0c2163c48cb13ec1a41fcf112137a62acc2760 Mon Sep 17 00:00:00 2001 From: Jimmy Shimizu Date: Mon, 5 Dec 2016 21:22:11 +0100 Subject: [PATCH 004/123] Support tri-state repeat --- lib/actions/playmode.js | 10 +++++++++- package.json | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/actions/playmode.js b/lib/actions/playmode.js index 8dcd101f..85cf5c33 100644 --- a/lib/actions/playmode.js +++ b/lib/actions/playmode.js @@ -1,5 +1,13 @@ function repeat(player, values) { - return player.coordinator.repeat(values[0] == "on" ? true : false); + let mode = values[0]; + + if (mode === "on") { + mode = "all"; + } else if (mode === "off") { + mode = "none"; + } + + return player.coordinator.repeat(mode); } function shuffle(player, values) { diff --git a/package.json b/package.json index c6547caf..80a77ae5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sonos-http-api", - "version": "1.2.3", + "version": "1.2.4", "description": "A simple node app for controlling a Sonos system with basic HTTP requests", "scripts": { "start": "node server.js" From 46fb0562afb60e1cbada5055521de500ccd21cb5 Mon Sep 17 00:00:00 2001 From: Jimmy Shimizu Date: Mon, 5 Dec 2016 21:55:51 +0100 Subject: [PATCH 005/123] Support mute in presets Resolves #153 Having a mute flag on the player specification in a preset will now turn it on off baed on boolean flag. --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 80a77ae5..e8932e29 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sonos-http-api", - "version": "1.2.4", + "version": "1.2.5", "description": "A simple node app for controlling a Sonos system with basic HTTP requests", "scripts": { "start": "node server.js" @@ -17,7 +17,7 @@ "fuse.js": "^2.2.0", "node-static": "~0.7.0", "request-promise": "~1.0.2", - "sonos-discovery": "https://github.com/jishi/node-sonos-discovery/archive/v1.2.2.tar.gz" + "sonos-discovery": "https://github.com/jishi/node-sonos-discovery/archive/v1.2.5.tar.gz" }, "engines": { "node": ">=4.0.0", From aac2b9f5dedea808cb9acae303ff0fca60c04979 Mon Sep 17 00:00:00 2001 From: Jimmy Shimizu Date: Mon, 5 Dec 2016 22:50:09 +0100 Subject: [PATCH 006/123] use strict everywhere --- lib/actions/appleMusic.js | 2 +- lib/actions/favorite.js | 1 + lib/actions/lockvolumes.js | 1 + lib/actions/musicSearch.js | 1 + lib/actions/mute.js | 1 + lib/actions/nextprevious.js | 1 + lib/actions/pauseall.js | 1 + lib/actions/playlists.js | 1 + lib/actions/playmode.js | 1 + lib/actions/playpause.js | 1 + lib/actions/reindex.js | 1 + lib/actions/seek.js | 1 + lib/actions/setavtransporturi.js | 1 + lib/actions/volume.js | 1 + lib/music_services/appleDef.js | 1 + lib/music_services/deezerDef.js | 1 + lib/music_services/spotifyDef.js | 1 + 17 files changed, 17 insertions(+), 1 deletion(-) diff --git a/lib/actions/appleMusic.js b/lib/actions/appleMusic.js index 2842df77..8992fc0b 100644 --- a/lib/actions/appleMusic.js +++ b/lib/actions/appleMusic.js @@ -1,4 +1,4 @@ - +'use strict'; function getMetadata(id, parentUri, type, title) { return ` diff --git a/lib/actions/favorite.js b/lib/actions/favorite.js index 59bf326f..e16394c3 100644 --- a/lib/actions/favorite.js +++ b/lib/actions/favorite.js @@ -1,3 +1,4 @@ +'use strict'; function favorite(player, values) { return player.coordinator.replaceWithFavorite(decodeURIComponent(values[0])) .then(() => player.coordinator.play()); diff --git a/lib/actions/lockvolumes.js b/lib/actions/lockvolumes.js index 7245ab87..9019a394 100644 --- a/lib/actions/lockvolumes.js +++ b/lib/actions/lockvolumes.js @@ -1,3 +1,4 @@ +'use strict'; var lockVolumes = {}; function lockvolumes(player) { diff --git a/lib/actions/musicSearch.js b/lib/actions/musicSearch.js index 3e8522dc..68c13e83 100644 --- a/lib/actions/musicSearch.js +++ b/lib/actions/musicSearch.js @@ -1,3 +1,4 @@ +'use strict'; const request = require('request-promise'); const fs = require("fs"); const isRadioOrLineIn = require('../helpers/is-radio-or-line-in'); diff --git a/lib/actions/mute.js b/lib/actions/mute.js index 4fdf2395..3cdacd2a 100644 --- a/lib/actions/mute.js +++ b/lib/actions/mute.js @@ -1,3 +1,4 @@ +'use strict'; function mute(player) { return player.mute(); } diff --git a/lib/actions/nextprevious.js b/lib/actions/nextprevious.js index 6cab0283..236a4eb0 100644 --- a/lib/actions/nextprevious.js +++ b/lib/actions/nextprevious.js @@ -1,3 +1,4 @@ +'use strict'; function next(player) { return player.coordinator.nextTrack(); } diff --git a/lib/actions/pauseall.js b/lib/actions/pauseall.js index 3c3c7796..b4837967 100644 --- a/lib/actions/pauseall.js +++ b/lib/actions/pauseall.js @@ -1,3 +1,4 @@ +'use strict'; var pausedPlayers = []; function pauseAll(player, values) { diff --git a/lib/actions/playlists.js b/lib/actions/playlists.js index b7b7e5b3..4e068d3a 100644 --- a/lib/actions/playlists.js +++ b/lib/actions/playlists.js @@ -1,3 +1,4 @@ +'use strict'; function playlists(player, values) { return player.system.getPlaylists() diff --git a/lib/actions/playmode.js b/lib/actions/playmode.js index 85cf5c33..ff13d88e 100644 --- a/lib/actions/playmode.js +++ b/lib/actions/playmode.js @@ -1,3 +1,4 @@ +'use strict'; function repeat(player, values) { let mode = values[0]; diff --git a/lib/actions/playpause.js b/lib/actions/playpause.js index 5755ad07..ba40b71e 100644 --- a/lib/actions/playpause.js +++ b/lib/actions/playpause.js @@ -1,3 +1,4 @@ +'use strict'; function playpause(player) { console.log(player.coordinator.state.playbackState) if(player.coordinator.state.playbackState === 'PLAYING') { diff --git a/lib/actions/reindex.js b/lib/actions/reindex.js index c5aaebdc..16437e48 100644 --- a/lib/actions/reindex.js +++ b/lib/actions/reindex.js @@ -1,3 +1,4 @@ +'use strict'; function reindex(player) { return player.system.refreshShareIndex(); } diff --git a/lib/actions/seek.js b/lib/actions/seek.js index cce8e516..27e31288 100644 --- a/lib/actions/seek.js +++ b/lib/actions/seek.js @@ -1,3 +1,4 @@ +'use strict'; function timeSeek(player, values) { return player.coordinator.timeSeek(values[0]); } diff --git a/lib/actions/setavtransporturi.js b/lib/actions/setavtransporturi.js index 80275306..885b68dc 100644 --- a/lib/actions/setavtransporturi.js +++ b/lib/actions/setavtransporturi.js @@ -1,3 +1,4 @@ +'use strict'; function setAVTransportURI(player, values) { return player.setAVTransport(decodeURIComponent(values[0])); } diff --git a/lib/actions/volume.js b/lib/actions/volume.js index 2c90e841..31fe8eb2 100644 --- a/lib/actions/volume.js +++ b/lib/actions/volume.js @@ -1,3 +1,4 @@ +'use strict'; function volume(player, values) { var volume = values[0]; return player.setVolume(volume); diff --git a/lib/music_services/appleDef.js b/lib/music_services/appleDef.js index 247a605f..1b6a621a 100644 --- a/lib/music_services/appleDef.js +++ b/lib/music_services/appleDef.js @@ -1,3 +1,4 @@ +'use strict'; const appleDef = { country: '&country=', search: { diff --git a/lib/music_services/deezerDef.js b/lib/music_services/deezerDef.js index ca4637a6..49779fd8 100644 --- a/lib/music_services/deezerDef.js +++ b/lib/music_services/deezerDef.js @@ -1,3 +1,4 @@ +'use strict'; const deezerDef = { country: '', search: { diff --git a/lib/music_services/spotifyDef.js b/lib/music_services/spotifyDef.js index bec4b97c..0289ba16 100644 --- a/lib/music_services/spotifyDef.js +++ b/lib/music_services/spotifyDef.js @@ -1,3 +1,4 @@ +'use strict'; const spotifyDef = { country: '&market=', search: { From f5e54754c2dc634e2492cd76a923b27198d20927 Mon Sep 17 00:00:00 2001 From: Jimmy Shimizu Date: Tue, 6 Dec 2016 14:22:30 +0100 Subject: [PATCH 007/123] Update sayall.js --- lib/actions/sayall.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/actions/sayall.js b/lib/actions/sayall.js index d2b9fea8..d40a2ad9 100644 --- a/lib/actions/sayall.js +++ b/lib/actions/sayall.js @@ -7,7 +7,7 @@ let port; let system; function sayAll(player, values) { - const text = values[0]; + const text = decodeURIComponent(values[0]); let announceVolume; let language; @@ -31,4 +31,4 @@ function sayAll(player, values) { module.exports = function (api) { port = api.getPort(); api.registerAction('sayall', sayAll); -}; \ No newline at end of file +}; From 6980261ab8a646a325b0eb716a3b775e1461515d Mon Sep 17 00:00:00 2001 From: Phil Hawthorne Date: Sun, 11 Dec 2016 21:24:50 +1100 Subject: [PATCH 008/123] Add Google TTS Provider Adds a Google Translate TTS Provider, allowing users to use TTS without an API key from an existing provider. --- README.md | 65 +++++++++++++++++++++++++++++++++++++ lib/tts-providers/google.js | 48 +++++++++++++++++++++++++++ 2 files changed, 113 insertions(+) create mode 100644 lib/tts-providers/google.js diff --git a/README.md b/README.md index 6cbd9257..5d519f34 100644 --- a/README.md +++ b/README.md @@ -365,6 +365,7 @@ Experimental support for TTS. Today the following providers are available: * voicerss * Microsoft Cognitive Services (Bing Text to Speech API) * AWS Polly +* Google It will use the one you configure in settings.json. If you define settings for multiple TTS services, it will not be guaranteed which one it will choose! @@ -539,6 +540,70 @@ This is the current list of voice names and their corresponding language and acc | Welsh | cy-GB | Female | Gwyneth | | Welsh | English | en-GB-WLS | Male | Geraint | +#### Google + +Does not require any API keys. Please note that Google has been known in the past to change the requirements for its Text-to-Speech API, and this may stop working in the future. + +The following language codes are supported + +| Language code | Language | +| ------------- | -------- | +| af | Afrikaans | +| sq | Albanian | +| ar | Arabic | +| hy | Armenian | +| bn | Bengali | +| ca | Catalan | +| zh | Chinese | +| zh-cn | Chinese (Mandarin/China) | +| zh-tw | Chinese (Mandarin/Taiwan) | +| zh-yue | Chinese (Cantonese) | +| hr | Croatian | +| cs | Czech | +| da | Danish | +| nl | Dutch | +| en | English | +| en-au | English (Australia) | +| en-gb | English (Great Britain) | +| en-us | English (United States) | +| eo | Esperanto | +| fi | Finnish | +| fr | Franch | +| de | German | +| el | Greek | +| hi | Hindi | +| hu | Hungarian | +| is | Icelandic | +| id | Indonesian | +| it | Italian | +| ja | Japanese | +| ko | Korean | +| la | Latin | +| lv | Latvian | +| mk | Macedonian | +| no | Norwegian | +| pl | Polish | +| pt | Portuguese | +| pt-br | Portuguese (Brazil) | +| ro | Romanian | +| ru | Russian | +| sr | Serbian | +| sk | Slovak | +| es | Spanish | +| es-es | Spanish (Spain) | +| es-us | Spanish (United States) | +| sw | Swahili | +| sv | Swedish | +| ta | Tamil | +| th | Thai | +| tr | Turkish | +| vi | Vietnamese | +| cy | Welsh | + +Action is: + + /[Room name]/say/[phrase][/[language_code]][/[announce volume]] + /sayall/[phrase][/[language_code]][/[announce volume]] Line-in ------- diff --git a/lib/tts-providers/google.js b/lib/tts-providers/google.js new file mode 100644 index 00000000..f5a062ef --- /dev/null +++ b/lib/tts-providers/google.js @@ -0,0 +1,48 @@ +'use strict'; +const crypto = require('crypto'); +const fs = require('fs'); +const http = require('http'); +const path = require('path'); +const settings = require('../../settings'); + +function google(phrase, language) { + // Use Google tts translation service to create a mp3 file + const ttsRequestUrl = 'http://translate.google.com/translate_tts?client=tw-ob&tl=' + language + '&q=' + phrase; + + // Construct a filesystem neutral filename + const phraseHash = crypto.createHash('sha1').update(phrase).digest('hex'); + const filename = `google-${phraseHash}-${language}.mp3`; + const filepath = path.resolve(settings.webroot, 'tts', filename); + + const expectedUri = `/tts/${filename}`; + try { + fs.accessSync(filepath, fs.R_OK); + return Promise.resolve(expectedUri); + } catch (err) { + console.log(`announce file for phrase "${phrase}" does not seem to exist, downloading`); + } + + return new Promise((resolve, reject) => { + var file = fs.createWriteStream(filepath); + var options = {"headers": {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36"}, "host": "translate.google.com", "path": "/translate_tts?client=tw-ob&tl=" + language + "&q=" + encodeURIComponent(phrase) } + var callback = function (response) { + if (response.statusCode < 300 && response.statusCode >= 200) { + response.pipe(file); + file.on('finish', function () { + file.end(); + resolve(expectedUri); + }); + } else { + reject(new Error(`Download from google TTS failed with status ${response.statusCode}, ${response.message}`)); + + } + } + + http.request(options, callback).on('error', function (err) { + fs.unlink(dest); + reject(err); + }).end(); + }); +} + +module.exports = google; \ No newline at end of file From 78e2cdb692eda0ec3bf1609ae5430fcd4f844efc Mon Sep 17 00:00:00 2001 From: Jimmy Shimizu Date: Sun, 11 Dec 2016 14:07:07 +0100 Subject: [PATCH 009/123] Merge PR #403, adding google as default TTS When no other TTS provider is configured, use Google TTS Allow VoiceId spec in URL for AWS Polly --- README.md | 24 ++++++++++++++++++----- lib/actions/say.js | 4 ++-- lib/actions/sayall.js | 4 ++-- lib/helpers/try-download-tts.js | 2 ++ lib/tts-providers/aws-polly.js | 10 +++++++--- lib/tts-providers/{ => default}/google.js | 8 ++++++-- lib/tts-providers/voicerss.js | 4 ++++ package.json | 2 +- 8 files changed, 43 insertions(+), 15 deletions(-) rename lib/tts-providers/{ => default}/google.js (91%) diff --git a/README.md b/README.md index 5d519f34..1d05d38c 100644 --- a/README.md +++ b/README.md @@ -365,7 +365,7 @@ Experimental support for TTS. Today the following providers are available: * voicerss * Microsoft Cognitive Services (Bing Text to Speech API) * AWS Polly -* Google +* Google (default) It will use the one you configure in settings.json. If you define settings for multiple TTS services, it will not be guaranteed which one it will choose! @@ -488,7 +488,21 @@ If you have your credentials elsewhere and want to stick with the default voice, } ``` -This is the current list of voice names and their corresponding language and accent +Action is: + + /[Room name]/say/[phrase][/[name]][/[announce volume]] + /sayall/[phrase][/[name]][/[announce volume]] + +Example: + + /Office/say/Hello, dinner is ready + /Office/say/Hej, maten är klar/Joanna + /sayall/Hello, dinner is ready + /Office/say/Hello, dinner is ready/90 + /Office/say/Hej, maten är klar/Russell/90 + +This is the current list of voice names and their corresponding language and accent (as of Dec 2016). +To get a current list of voices, you would need to use the AWS CLI and invoke the describe-voices command. | Language | Code | Gender | Name | | --------- | ---- | ------ | ---- | @@ -538,11 +552,11 @@ This is the current list of voice names and their corresponding language and acc | US Spanish | es-US | Female | Penelope | | US Spanish | es-US | Male | Miguel | | Welsh | cy-GB | Female | Gwyneth | -| Welsh | English | en-GB-WLS | Male | Geraint | +| Welsh English | en-GB-WLS | Male | Geraint | -#### Google +#### Google (default if no other has been configured) -Does not require any API keys. Please note that Google has been known in the past to change the requirements for its Text-to-Speech API, and this may stop working in the future. +Does not require any API keys. Please note that Google has been known in the past to change the requirements for its Text-to-Speech API, and this may stop working in the future. There is also limiations to have many request one is allowed to do in a specific time period. The following language codes are supported diff --git a/lib/actions/say.js b/lib/actions/say.js index 71aa9ef7..bf3940c1 100644 --- a/lib/actions/say.js +++ b/lib/actions/say.js @@ -18,9 +18,9 @@ function say(player, values) { if (/^\d+$/i.test(values[1])) { // first parameter is volume announceVolume = values[1]; - language = 'en-gb'; + // language = 'en-gb'; } else { - language = values[1] || 'en-gb'; + language = values[1]; announceVolume = values[2] || settings.announceVolume || 40; } diff --git a/lib/actions/sayall.js b/lib/actions/sayall.js index d40a2ad9..ecd31dd1 100644 --- a/lib/actions/sayall.js +++ b/lib/actions/sayall.js @@ -14,9 +14,9 @@ function sayAll(player, values) { if (/^\d+$/i.test(values[1])) { // first parameter is volume announceVolume = values[1]; - language = 'en-gb'; + // language = 'en-gb'; } else { - language = values[1] || 'en-gb'; + language = values[1]; announceVolume = values[2] || settings.announceVolume || 40; } diff --git a/lib/helpers/try-download-tts.js b/lib/helpers/try-download-tts.js index dc97c209..ad2f687f 100644 --- a/lib/helpers/try-download-tts.js +++ b/lib/helpers/try-download-tts.js @@ -7,6 +7,8 @@ requireDir(path.join(__dirname, '../tts-providers'), (provider) => { providers.push(provider); }); +providers.push(require('../tts-providers/default/google')); + function tryDownloadTTS(phrase, language) { let path; return providers.reduce((promise, provider) => { diff --git a/lib/tts-providers/aws-polly.js b/lib/tts-providers/aws-polly.js index ef69690f..a38f094f 100644 --- a/lib/tts-providers/aws-polly.js +++ b/lib/tts-providers/aws-polly.js @@ -12,7 +12,7 @@ const DEFAULT_SETTINGS = { TextType: 'text' }; -function polly(phrase, language) { +function polly(phrase, voiceName) { if (!settings.aws) { return Promise.resolve(); @@ -20,10 +20,14 @@ function polly(phrase, language) { // Construct a filesystem neutral filename const dynamicParameters = { Text: phrase }; + const synthesizeParameters = Object.assign({}, DEFAULT_SETTINGS, dynamicParameters); if (settings.aws.name) { - dynamicParameters.VoiceId = settings.aws.name; + synthesizeParameters.VoiceId = settings.aws.name; } - const synthesizeParameters = Object.assign({}, DEFAULT_SETTINGS, dynamicParameters); + if (voiceName) { + synthesizeParameters.VoiceId = voiceName; + } + const phraseHash = crypto.createHash('sha1').update(phrase).digest('hex'); const filename = `polly-${phraseHash}-${synthesizeParameters.VoiceId}.mp3`; const filepath = path.resolve(settings.webroot, 'tts', filename); diff --git a/lib/tts-providers/google.js b/lib/tts-providers/default/google.js similarity index 91% rename from lib/tts-providers/google.js rename to lib/tts-providers/default/google.js index f5a062ef..61483b3e 100644 --- a/lib/tts-providers/google.js +++ b/lib/tts-providers/default/google.js @@ -3,11 +3,15 @@ const crypto = require('crypto'); const fs = require('fs'); const http = require('http'); const path = require('path'); -const settings = require('../../settings'); +const settings = require('../../../settings'); function google(phrase, language) { + if (!language) { + language = 'en'; + } + // Use Google tts translation service to create a mp3 file - const ttsRequestUrl = 'http://translate.google.com/translate_tts?client=tw-ob&tl=' + language + '&q=' + phrase; + const ttsRequestUrl = 'http://translate.google.com/translate_tts?client=tw-ob&tl=' + language + '&q=' + encodeURIComponent(phrase); // Construct a filesystem neutral filename const phraseHash = crypto.createHash('sha1').update(phrase).digest('hex'); diff --git a/lib/tts-providers/voicerss.js b/lib/tts-providers/voicerss.js index 4d6786a9..57df31d5 100644 --- a/lib/tts-providers/voicerss.js +++ b/lib/tts-providers/voicerss.js @@ -10,6 +10,10 @@ function voicerss(phrase, language) { return Promise.resolve(); } + + if (!language) { + language = 'en-gb'; + } // Use voicerss tts translation service to create a mp3 file const ttsRequestUrl = `http://api.voicerss.org/?key=${settings.voicerss}&f=22khz_16bit_mono&hl=${language}&src=${encodeURIComponent(phrase)}`; diff --git a/package.json b/package.json index e8932e29..1d811552 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sonos-http-api", - "version": "1.2.5", + "version": "1.2.6", "description": "A simple node app for controlling a Sonos system with basic HTTP requests", "scripts": { "start": "node server.js" From a49f35b3a86bd7daf11483b2a1a718952ea13b8e Mon Sep 17 00:00:00 2001 From: Jimmy Shimizu Date: Sun, 11 Dec 2016 14:58:43 +0100 Subject: [PATCH 010/123] BREAKING CHANGE: Allow specifying voice name for Microsoft TTS Reference voices by name. --- README.md | 37 +++++++++++++++++------- lib/actions/say.js | 10 ++++++- lib/actions/sayall.js | 10 ++++++- lib/tts-providers/microsoft.js | 53 +++++++++++++++++++++++++++++----- 4 files changed, 90 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 1d05d38c..ab203bfc 100644 --- a/README.md +++ b/README.md @@ -305,9 +305,7 @@ Example: "voicerss": "Your api key for TTS with voicerss", "microsoft": { "key": "Your api for Bing speech API", - "gender": "Female", - "name": "Microsoft Server Speech Text to Speech Voice (en-US, ZiraRUS)", - "language": "en-US" + "name": "ZiraRUS" }, "port": 5005, "securePort": 5006, @@ -440,25 +438,41 @@ The following configuration is available (the entered values except key are defa { "microsoft": { "key": "Your api for Bing speech API", - "gender": "Female", - "name": "Microsoft Server Speech Text to Speech Voice (en-US, ZiraRUS)", - "language": "en-US" + "name": "ZiraRUS" } } ``` -If you change language, you need to change the name matching the gender for that language, according to this list: https://www.microsoft.com/cognitive-services/en-us/speech-api/documentation/API-Reference-REST/BingVoiceOutput#SupLocales. This one doesn't support providing language directly in the request for this reason. +You change language by specifying a voice name correlating to the desired language. +Name should be specified according to this list: https://www.microsoft.com/cognitive-services/en-us/speech-api/documentation/API-Reference-REST/BingVoiceOutput#SupLocales +where name is the right most part of the voice font name (without optional Apollo suffix). Example: + +`Microsoft Server Speech Text to Speech Voice (ar-EG, Hoda)` name should be specified as `Hoda` + +`Microsoft Server Speech Text to Speech Voice (de-DE, Stefan, Apollo)` name should be specified as `Stefan` + +`Microsoft Server Speech Text to Speech Voice (en-US, BenjaminRUS)` name should be specified as `BenjaminRUS` Action is: - /[Room name]/say/[phrase][/[announce volume]] - /sayall/[phrase][/[announce volume]] + /[Room name]/say/[phrase][/[name]][/[announce volume]] + /sayall/[phrase][/[name]][/[announce volume]] Example: /Office/say/Hello, dinner is ready + /Office/say/Hello, dinner is ready/BenjaminRUS + /Office/say/Guten morgen/Stefan /sayall/Hello, dinner is ready /Office/say/Hello, dinner is ready/90 + /Office/say/Guten morgen/Stefan/90 + +Supported voices are: + +Hoda, Hedda, Stefan, Catherine, Linda, Susan, George, Ravi, ZiraRUS, BenjaminRUS, Laura, Pablo, Raul, Caroline, Julie, Paul, Cosimo, Ayumi, Ichiro, Daniel, Irina, Pavel, HuihuiRUS, Yaoyao, Kangkang, Tracy, Danny, Yating, Zhiwei + +See https://www.microsoft.com/cognitive-services/en-us/speech-api/documentation/API-Reference-REST/BingVoiceOutput#SupLocales to identify +which language and gender it maps against. #### AWS Polly @@ -496,10 +510,11 @@ Action is: Example: /Office/say/Hello, dinner is ready - /Office/say/Hej, maten är klar/Joanna + /Office/say/Hello, dinner is ready/Nicole + /Office/say/Hej, maten är klar/Astrid /sayall/Hello, dinner is ready /Office/say/Hello, dinner is ready/90 - /Office/say/Hej, maten är klar/Russell/90 + /Office/say/Hej, maten är klar/Astrid/90 This is the current list of voice names and their corresponding language and accent (as of Dec 2016). To get a current list of voices, you would need to use the AWS CLI and invoke the describe-voices command. diff --git a/lib/actions/say.js b/lib/actions/say.js index bf3940c1..d5e5b936 100644 --- a/lib/actions/say.js +++ b/lib/actions/say.js @@ -11,7 +11,15 @@ let port; let system; function say(player, values) { - const text = decodeURIComponent(values[0]); + let text; + try { + text = decodeURIComponent(values[0]); + } catch (err) { + if (err instanceof URIError) { + err.message = `The encoded phrase ${values[0]} could not be URI decoded. Make sure your url encoded values (%xx) are within valid ranges. xx should be hexadecimal representations`; + } + return Promise.reject(err); + } let announceVolume; let language; diff --git a/lib/actions/sayall.js b/lib/actions/sayall.js index ecd31dd1..ec1a8ea4 100644 --- a/lib/actions/sayall.js +++ b/lib/actions/sayall.js @@ -7,7 +7,15 @@ let port; let system; function sayAll(player, values) { - const text = decodeURIComponent(values[0]); + let text; + try { + text = decodeURIComponent(values[0]); + } catch (err) { + if (err instanceof URIError) { + err.message = `The encoded phrase ${values[0]} could not be URI decoded. Make sure your url encoded values (%xx) are within valid ranges. xx should be hexadecimal representations`; + } + return Promise.reject(err); + } let announceVolume; let language; diff --git a/lib/tts-providers/microsoft.js b/lib/tts-providers/microsoft.js index a48d615b..dd991e94 100644 --- a/lib/tts-providers/microsoft.js +++ b/lib/tts-providers/microsoft.js @@ -10,9 +10,7 @@ const APP_ID = '9aa44d9e6ec14da99231a9166fd50b0f'; const INSTANCE_ID = crypto.randomBytes(16).toString('hex'); const TOKEN_EXPIRATION = 590000; // 9:50 minutes in ms const DEFAULT_SETTINGS = { - language: 'en-US', - gender: 'Female', - name: 'Microsoft Server Speech Text to Speech Voice (en-US, ZiraRUS)' + name: 'ZiraRUS' }; let bearerToken; @@ -39,15 +37,19 @@ function format(lang, gender, name, text) { return `${text}`; } -function microsoft(phrase, language) { +function microsoft(phrase, voiceName) { if (!globalSettings.microsoft || !globalSettings.microsoft.key) { return Promise.resolve(); } const settings = Object.assign({}, DEFAULT_SETTINGS, globalSettings.microsoft); + if (voiceName) { + settings.name = voiceName; + } + const phraseHash = crypto.createHash('sha1').update(phrase).digest('hex'); - const filename = `microsoft-${phraseHash}-${settings.language}-${settings.gender}.wav`; + const filename = `microsoft-${phraseHash}-${settings.name}.wav`; const filepath = path.resolve(globalSettings.webroot, 'tts', filename); const expectedUri = `/tts/${filename}`; @@ -65,7 +67,12 @@ function microsoft(phrase, language) { } return promise.then(() => { - const ssml = format(settings.language, settings.gender, settings.name, phrase); + const voice = VOICE[settings.name]; + if (!voice) { + throw new Error(`Voice name ${settings.name} could not be located in the list of valid voice names`); + } + + const ssml = format(voice.language, voice.gender, voice.font, phrase); return request({ uri: 'https://speech.platform.bing.com/synthesize', method: 'POST', @@ -99,4 +106,36 @@ function microsoft(phrase, language) { }); } -module.exports = microsoft; \ No newline at end of file +const VOICE = { + Hoda: { language: 'ar-EG', gender: 'Female', font: 'Microsoft Server Speech Text to Speech Voice (ar-EG, Hoda)' }, + Hedda: { language: 'de-DE', gender: 'Female', font: 'Microsoft Server Speech Text to Speech Voice (de-DE, Hedda)' }, + Stefan: { language: 'de-DE', gender: 'Male', font: 'Microsoft Server Speech Text to Speech Voice (de-DE, Stefan, Apollo)' }, + Catherine: { language: 'en-AU', gender: 'Female', font: 'Microsoft Server Speech Text to Speech Voice (en-AU, Catherine)' }, + Linda: { language: 'en-CA', gender: 'Female', font: 'Microsoft Server Speech Text to Speech Voice (en-CA, Linda)' }, + Susan: { language: 'en-GB', gender: 'Female', font: 'Microsoft Server Speech Text to Speech Voice (en-GB, Susan, Apollo)' }, + George: { language: 'en-GB', gender: 'Male', font: 'Microsoft Server Speech Text to Speech Voice (en-GB, George, Apollo)' }, + Ravi: { language: 'en-IN', gender: 'Male', font: 'Microsoft Server Speech Text to Speech Voice (en-IN, Ravi, Apollo)' }, + ZiraRUS: { language: 'en-US', gender: 'Female', font: 'Microsoft Server Speech Text to Speech Voice (en-US, ZiraRUS)' }, + BenjaminRUS: { language: 'en-US', gender: 'Male', font: 'Microsoft Server Speech Text to Speech Voice (en-US, BenjaminRUS)' }, + Laura: { language: 'es-ES', gender: 'Female', font: 'Microsoft Server Speech Text to Speech Voice (es-ES, Laura, Apollo)' }, + Pablo: { language: 'es-ES', gender: 'Male', font: 'Microsoft Server Speech Text to Speech Voice (es-ES, Pablo, Apollo)' }, + Raul: { language: 'es-MX', gender: 'Male', font: 'Microsoft Server Speech Text to Speech Voice (es-MX, Raul, Apollo)' }, + Caroline: { language: 'fr-CA', gender: 'Female', font: 'Microsoft Server Speech Text to Speech Voice (fr-CA, Caroline)' }, + Julie: { language: 'fr-FR', gender: 'Female', font: 'Microsoft Server Speech Text to Speech Voice (fr-FR, Julie, Apollo)' }, + Paul: { language: 'fr-FR', gender: 'Male', font: 'Microsoft Server Speech Text to Speech Voice (fr-FR, Paul, Apollo)' }, + Cosimo: { language: 'it-IT', gender: 'Male', font: 'Microsoft Server Speech Text to Speech Voice (it-IT, Cosimo, Apollo)' }, + Ayumi: { language: 'ja-JP', gender: 'Female', font: 'Microsoft Server Speech Text to Speech Voice (ja-JP, Ayumi, Apollo)' }, + Ichiro: { language: 'ja-JP', gender: 'Male', font: 'Microsoft Server Speech Text to Speech Voice (ja-JP, Ichiro, Apollo)' }, + Daniel: { language: 'pt-BR', gender: 'Male', font: 'Microsoft Server Speech Text to Speech Voice (pt-BR, Daniel, Apollo)' }, + Irina: { language: 'ru-RU', gender: 'Female', font: 'Microsoft Server Speech Text to Speech Voice (ru-RU, Irina, Apollo)' }, + Pavel: { language: 'ru-RU', gender: 'Male', font: 'Microsoft Server Speech Text to Speech Voice (ru-RU, Pavel, Apollo)' }, + HuihuiRUS: { language: 'zh-CN', gender: 'Female', font: 'Microsoft Server Speech Text to Speech Voice (zh-CN, HuihuiRUS)' }, + Yaoyao: { language: 'zh-CN', gender: 'Female', font: 'Microsoft Server Speech Text to Speech Voice (zh-CN, Yaoyao, Apollo)' }, + Kangkang: { language: 'zh-CN', gender: 'Male', font: 'Microsoft Server Speech Text to Speech Voice (zh-CN, Kangkang, Apollo)' }, + Tracy: { language: 'zh-HK', gender: 'Female', font: 'Microsoft Server Speech Text to Speech Voice (zh-HK, Tracy, Apollo)' }, + Danny: { language: 'zh-HK', gender: 'Male', font: 'Microsoft Server Speech Text to Speech Voice (zh-HK, Danny, Apollo)' }, + Yating: { language: 'zh-TW', gender: 'Female', font: 'Microsoft Server Speech Text to Speech Voice (zh-TW, Yating, Apollo)' }, + Zhiwei: { language: 'zh-TW', gender: 'Male', font: 'Microsoft Server Speech Text to Speech Voice (zh-TW, Zhiwei, Apollo)' } +}; + +module.exports = microsoft; From 92ec99c1a8e2ef551971a44a6b5900d4385a7b06 Mon Sep 17 00:00:00 2001 From: mattywong Date: Fri, 23 Dec 2016 02:15:23 +0800 Subject: [PATCH 011/123] added "togglemute" command to toggle mute state (#415) * added togglemute command * added doc specs * updated readme * Update README.md * Update mute.js * Update index.html --- README.md | 1 + lib/actions/mute.js | 9 +++++++++ static/index.html | 1 + 3 files changed, 11 insertions(+) diff --git a/README.md b/README.md index ab203bfc..548b863c 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,7 @@ The actions supported as of today: * groupVolume (parameter is absolute or relative volume. Prefix +/- indicates relative volume) * mute / unmute * groupMute / groupUnmute +* togglemute (toggles mute state) * trackseek (parameter is queue index) * timeseek (parameter is in seconds, 60 for 1:00, 120 for 2:00 etc) * next diff --git a/lib/actions/mute.js b/lib/actions/mute.js index 3cdacd2a..d53ee7f4 100644 --- a/lib/actions/mute.js +++ b/lib/actions/mute.js @@ -15,6 +15,14 @@ function groupUnmute(player) { return player.coordinator.unMuteGroup(); } +function toggleMute(player) { + if(player.coordinator.state.mute) { + return player.unMute(); + }; + + return player.mute(); +} + module.exports = function (api) { api.registerAction('mute', mute); api.registerAction('unmute', unmute); @@ -22,4 +30,5 @@ module.exports = function (api) { api.registerAction('groupunmute', groupUnmute); api.registerAction('mutegroup', groupMute); api.registerAction('unmutegroup', groupUnmute); + api.registerAction('togglemute', toggleMute); } diff --git a/static/index.html b/static/index.html index fb2b2846..e2ef1a1b 100644 --- a/static/index.html +++ b/static/index.html @@ -55,6 +55,7 @@

Actions

  • groupVolume (parameter is absolute or relative volume. Prefix +/- indicates relative volume)
  • mute / unmute
  • groupMute / groupUnmute
  • +
  • togglemute (toggles mute state)
  • seek (parameter is queue index)
  • trackseek (parameter is in seconds, 60 for 1:00, 120 for 2:00 etc)
  • next
  • From 1fc020e4c0f7fff743937d6b522cc59fcd739865 Mon Sep 17 00:00:00 2001 From: Chris Nesbitt-Smith Date: Thu, 5 Jan 2017 15:06:06 +0000 Subject: [PATCH 012/123] Dockerize (#428) * add docker file * update readme to document docker usage * add docker usage presets to README * add a dockerignore file * run docker container in daemon mode * remove dockerfile and reference our build --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 548b863c..ffd4d887 100644 --- a/README.md +++ b/README.md @@ -830,3 +830,7 @@ or "data" property will be equal to the same data as you would get from /RoomName/state or /zones. There is an example endpoint in the root if this project called test_endpoint.js which you may fire up to get an understanding of what is posted, just invoke it with "node test_endpoint.js" in a terminal, and then start the http-api in another terminal. +DOCKER +----- + +Docker usage is maintained by [Chris Nesbitt-Smith](https://github.com/chrisns) at [chrisns/docker-node-sonos-http-api](https://github.com/chrisns/docker-node-sonos-http-api) \ No newline at end of file From 0202420c0b24cada7ddee3e3966e9c8171c4d75b Mon Sep 17 00:00:00 2001 From: dandiddy Date: Sat, 7 Jan 2017 11:23:05 +0000 Subject: [PATCH 013/123] Making toggleMute toggle when player is grouped (#430) mute is a single player action, so it should only check status and toggle for that specific player, not coordinator. --- lib/actions/mute.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/actions/mute.js b/lib/actions/mute.js index d53ee7f4..efa6c938 100644 --- a/lib/actions/mute.js +++ b/lib/actions/mute.js @@ -16,7 +16,7 @@ function groupUnmute(player) { } function toggleMute(player) { - if(player.coordinator.state.mute) { + if(player.state.mute) { return player.unMute(); }; From 5e36dd13244a60a34c1fe06aaae40ebe309b152b Mon Sep 17 00:00:00 2001 From: Jimmy Shimizu Date: Tue, 24 Jan 2017 20:18:57 +0100 Subject: [PATCH 014/123] increased logging --- lib/music_services/libraryDef.js | 10 +++++++--- package.json | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/music_services/libraryDef.js b/lib/music_services/libraryDef.js index a1351c8e..8c7d62b2 100644 --- a/lib/music_services/libraryDef.js +++ b/lib/music_services/libraryDef.js @@ -4,6 +4,7 @@ const fs = require('fs'); const path = require('path'); const settings = require('../../settings'); const libraryPath = path.join(settings.cacheDir, 'library.json'); +const logger = require('sonos-discovery/lib/helpers/logger'); var randomQueueLimit = (settings.library && settings.library.randomQueueLimit !== undefined)?settings.library.randomQueueLimit:50; @@ -151,7 +152,7 @@ function loadLibrary(player) { if (isLoading) { return Promise.resolve('Loading'); } - console.log("Loading Library"); + logger.info('Loading Library'); isLoading = true; let library = { @@ -186,7 +187,7 @@ function loadLibrary(player) { result.numberReturned += chunk.numberReturned; result.totalMatches = chunk.totalMatches; - console.log("Track Count " + result.numberReturned); + logger.info(`Tracks returned: ${result.numberReturned}, Total matches: ${result.totalMatches}`); if (isFinished(chunk)) { return new Promise((resolve, reject) => { @@ -208,7 +209,10 @@ function loadLibrary(player) { } return Promise.resolve(result) - .then(getChunk); + .then(getChunk) + .catch((err) => { + logger.error('Error when recursively trying to load library using browse()', err); + }); } function loadLibrarySearch(player, load) { diff --git a/package.json b/package.json index 1d811552..3aacaf37 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "fuse.js": "^2.2.0", "node-static": "~0.7.0", "request-promise": "~1.0.2", - "sonos-discovery": "https://github.com/jishi/node-sonos-discovery/archive/v1.2.5.tar.gz" + "sonos-discovery": "https://github.com/jishi/node-sonos-discovery/archive/v1.2.6.tar.gz" }, "engines": { "node": ">=4.0.0", From 8ab02899648dfd673ba649b6285265041121d9ad Mon Sep 17 00:00:00 2001 From: Jimmy Shimizu Date: Sun, 29 Jan 2017 12:59:10 +0100 Subject: [PATCH 015/123] Create ISSUE_TEMPLATE.md --- ISSUE_TEMPLATE.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 ISSUE_TEMPLATE.md diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md new file mode 100644 index 00000000..dc033ea1 --- /dev/null +++ b/ISSUE_TEMPLATE.md @@ -0,0 +1,12 @@ +## Before you submit an issue + +Search among the current open _and_ closed issues for an answer to your question before submitting an issue! + +If your question is Docker related, please file the issue at the +https://github.com/chrisns/docker-node-sonos-http-api and also, make sure you have tested that image before asking a question. +This is the only image that has any correlation to this project, and is guaranteed to be up to date. + +If your question has anything to do with Amazon Echo, it is probably better suited at https://github.com/rgraciano/echo-sonos. If you have questions +about the requests sent to this library, figure out the exact request that is sent from the Alexa Skill (through the logs) and include that in the issue. + +Thank you! From c07c374a31d7668187937377b16e52df2365284c Mon Sep 17 00:00:00 2001 From: Jimmy Shimizu Date: Sun, 29 Jan 2017 13:00:16 +0100 Subject: [PATCH 016/123] Update ISSUE_TEMPLATE.md --- ISSUE_TEMPLATE.md | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md index dc033ea1..7e0d9acd 100644 --- a/ISSUE_TEMPLATE.md +++ b/ISSUE_TEMPLATE.md @@ -2,11 +2,8 @@ Search among the current open _and_ closed issues for an answer to your question before submitting an issue! -If your question is Docker related, please file the issue at the -https://github.com/chrisns/docker-node-sonos-http-api and also, make sure you have tested that image before asking a question. -This is the only image that has any correlation to this project, and is guaranteed to be up to date. +If your question is Docker related, please file the issue at the https://github.com/chrisns/docker-node-sonos-http-api and also, make sure you have tested that image before asking a question. This is the only image that has any correlation to this project, and is guaranteed to be up to date. -If your question has anything to do with Amazon Echo, it is probably better suited at https://github.com/rgraciano/echo-sonos. If you have questions -about the requests sent to this library, figure out the exact request that is sent from the Alexa Skill (through the logs) and include that in the issue. +If your question has anything to do with Amazon Echo, it is probably better suited at https://github.com/rgraciano/echo-sonos. If you have questions about the requests sent to this library, figure out the exact request that is sent from the Alexa Skill (through the logs) and include that in the issue. Thank you! From dd40a53e8e33185da300aa0a09e9727ee18ade66 Mon Sep 17 00:00:00 2001 From: Jimmy Shimizu Date: Tue, 31 Jan 2017 22:41:46 +0100 Subject: [PATCH 017/123] Fix lock volume feature --- lib/actions/lockvolumes.js | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/lib/actions/lockvolumes.js b/lib/actions/lockvolumes.js index 9019a394..26315960 100644 --- a/lib/actions/lockvolumes.js +++ b/lib/actions/lockvolumes.js @@ -1,31 +1,32 @@ 'use strict'; -var lockVolumes = {}; +const logger = require('sonos-discovery/lib/helpers/logger'); +const lockVolumes = {}; function lockvolumes(player) { - console.log("locking volumes"); + logger.debug('locking volumes'); // Locate all volumes var system = player.system; - for (var i in system.players) { - var player = system.players[i]; - lockVolumes[i] = player.state.volume; - } + system.players.forEach((player) => { + lockVolumes[player.uuid] = player.state.volume; + }); + // prevent duplicates, will ignore if no event listener is here - system.removeListener("volume", restrictVolume); - system.on("volume", restrictVolume); + system.removeListener('volume-change', restrictVolume); + system.on('volume-change', restrictVolume); return Promise.resolve(); } function unlockvolumes(player) { - console.log("unlocking volumes"); + logger.debug('unlocking volumes'); var system = player.system; - system.removeListener("volume", restrictVolume); + system.removeListener('volume-change', restrictVolume); return Promise.resolve(); } function restrictVolume(info) { - console.log("should revert volume to", lockVolumes[info.uuid]); - var player = this.getPlayerByUUID(info.uuid); + logger.debug(`should revert volume to ${lockVolumes[info.uuid]}`); + const player = this.getPlayerByUUID(info.uuid); // Only do this if volume differs if (player.state.volume != lockVolumes[info.uuid]) return player.setVolume(lockVolumes[info.uuid]); From a2e83c6c776993ebd492791f1efbfb546f02c6c2 Mon Sep 17 00:00:00 2001 From: Jimmy Shimizu Date: Sat, 21 Jan 2017 12:27:21 +0100 Subject: [PATCH 018/123] Experimental increase of reliability of TTS #Pleasi enter the commit message for your changes. Lines starting --- lib/helpers/all-player-announcement.js | 81 ++++++++++++++--------- lib/helpers/single-player-announcement.js | 71 +++++++++++++------- 2 files changed, 95 insertions(+), 57 deletions(-) diff --git a/lib/helpers/all-player-announcement.js b/lib/helpers/all-player-announcement.js index 054ff2e7..4a12696f 100644 --- a/lib/helpers/all-player-announcement.js +++ b/lib/helpers/all-player-announcement.js @@ -6,9 +6,9 @@ let globalListenerRegistered = false; function saveAll(system) { const backupPresets = system.zones.map((zone) => { - var coordinator = zone.coordinator; - var state = coordinator.state; - var preset = { + const coordinator = zone.coordinator; + const state = coordinator.state; + const preset = { players: [ { roomName: coordinator.roomName, volume: state.volume } ], @@ -37,17 +37,8 @@ function saveAll(system) { return backupPresets; } -function topologyChanged() { - if (onOneBigGroup instanceof Function) { - onOneBigGroup(); - } -} - function announceAll(system, uri, volume) { - if (!globalListenerRegistered) { - system.on('topology-change', topologyChanged); - globalListenerRegistered = true; - } + let abortTimer; // Save all players var backupPresets = saveAll(system); @@ -84,52 +75,76 @@ function announceAll(system, uri, volume) { let afterPlayingStateChange; const onTransportChange = (state) => { - logger.debug(coordinator.roomName, state.playbackState); - - if (state.playbackState === 'PLAYING') { - afterPlayingStateChange = announceFinished; + // Short circuit if this announcement has been finished. + if (!announceFinished) { + return; } + logger.debug(`playback state switched to ${state.playbackState}`); + // if (state.playbackState !== 'STOPPED') { + // player.once('transport-state', onTransportChange); + // } - if (state.playbackState !== "STOPPED") { + if (state.playbackState === 'STOPPED' && afterPlayingStateChange instanceof Function) { + logger.debug('announcement finished because of STOPPED state identified'); + afterPlayingStateChange(); + afterPlayingStateChange = undefined; return; } - if (afterPlayingStateChange instanceof Function) { - logger.debug('announcement finished'); - afterPlayingStateChange(); + if (state.playbackState === 'PLAYING') { + afterPlayingStateChange = announceFinished; } + + const abortDelay = coordinator._state.currentTrack.duration + 2; + clearTimeout(abortTimer); + logger.debug(`Setting restore timer for ${abortDelay} seconds`); + abortTimer = setTimeout(() => { + logger.debug(`Restoring backup preset because ${abortDelay} seconds passed`); + if (announceFinished instanceof Function) { + announceFinished(); + } + }, abortDelay * 1000); + + // This is some odd scenario where STOPPED is emitted when starting playback for some reason. + coordinator.once('transport-state', onTransportChange); }; + const oneGroupPromise = new Promise((resolve) => { + const onTopologyChanged = (topology) => { + if (topology.length === 1) { + return resolve(); + } + // Not one group yet, continue listening + system.once('topology-change', onTopologyChanged); + }; + + system.once('topology-change', onTopologyChanged); + }); + return system.applyPreset(preset) .then(() => { if (system.zones.length === 1) return; - - return new Promise((resolve) => { - onOneBigGroup = resolve; - }) - }) - .then(() => { - return coordinator.play(); + return oneGroupPromise; }) .then(() => { - coordinator.on('transport-state', onTransportChange); + coordinator.once('transport-state', onTransportChange); + coordinator.play(); return new Promise((resolve) => { announceFinished = resolve; }); }) .then(() => { - logger.debug('removing listener from', coordinator.roomName); - coordinator.removeListener('transport-state', onTransportChange); + clearTimeout(abortTimer); + announceFinished = undefined; }) .then(() => { - logger.debug(backupPresets); return backupPresets.reduce((promise, preset) => { return promise.then(() => system.applyPreset(preset)); }, Promise.resolve()); }) .catch((err) => { logger.error(err.stack); - coordinator.removeListener('transport-state', onTransportChange); + // coordinator.removeListener('transport-state', onTransportChange); }); } diff --git a/lib/helpers/single-player-announcement.js b/lib/helpers/single-player-announcement.js index 262e058e..7df96820 100644 --- a/lib/helpers/single-player-announcement.js +++ b/lib/helpers/single-player-announcement.js @@ -44,7 +44,7 @@ function singlePlayerAnnouncement(player, uri, volume) { backupPreset.uri = `x-rincon:${player.coordinator.uuid}`; } - logger.debug('backup preset was', backupPreset); + logger.debug('backup state was', backupPreset); // Use the preset action to play the tts file var ttsPreset = { @@ -59,43 +59,65 @@ function singlePlayerAnnouncement(player, uri, volume) { let announceFinished; let afterPlayingStateChange; + let abortTimer; const onTransportChange = (state) => { - logger.debug(`playback state switched to ${state.playbackState}`); - if (state.playbackState === 'PLAYING') { - logger.debug('I believe announcement starts here'); - afterPlayingStateChange = announceFinished; - } - - if (state.playbackState !== "STOPPED") { + // Short circuit if this announcement has been finished. + if (!announceFinished) { return; } + logger.debug(`playback state switched to ${state.playbackState}`); + // if (state.playbackState !== 'STOPPED') { + // player.once('transport-state', onTransportChange); + // } - if (afterPlayingStateChange instanceof Function) { + if (state.playbackState === 'STOPPED' && afterPlayingStateChange instanceof Function) { logger.debug('announcement finished because of STOPPED state identified'); afterPlayingStateChange(); + afterPlayingStateChange = undefined; + return; } - }; - let abortTimer; + if (state.playbackState === 'PLAYING') { + afterPlayingStateChange = announceFinished; + } + + const abortDelay = player._state.currentTrack.duration + 2; + clearTimeout(abortTimer); + logger.debug(`Setting restore timer for ${abortDelay} seconds`); + abortTimer = setTimeout(() => { + logger.debug(`Restoring backup preset because ${abortDelay} seconds passed`); + if (announceFinished instanceof Function) { + announceFinished(); + } + }, abortDelay * 1000); + + // This is some odd scenario where STOPPED is emitted when starting playback for some reason. + player.once('transport-state', onTransportChange); + }; if (!backupPresets[player.roomName]) { backupPresets[player.roomName] = []; } backupPresets[player.roomName].unshift(backupPreset); + logger.debug('backup presets array', backupPresets[player.roomName]); const prepareBackupPreset = () => { - const relevantBackupPreset = backupPresets[player.roomName].shift(); - - if (!relevantBackupPreset) { + if (backupPresets[player.roomName].length > 1) { + backupPresets[player.roomName].shift(); + logger.debug('more than 1 backup presets during prepare', backupPresets[player.roomName]); return Promise.resolve(); } - if (backupPresets[player.roomName].length > 0) { + if (backupPresets[player.roomName].length < 1) { return Promise.resolve(); } + const relevantBackupPreset = backupPresets[player.roomName][0]; + + logger.debug('exactly 1 preset left', relevantBackupPreset); + if (relevantBackupPreset.group) { const zone = system.zones.find(zone => zone.id === relevantBackupPreset.group); if (zone) { @@ -104,29 +126,30 @@ function singlePlayerAnnouncement(player, uri, volume) { } logger.debug('applying preset', relevantBackupPreset); - return system.applyPreset(relevantBackupPreset); + return system.applyPreset(relevantBackupPreset) + .then(() => { + backupPresets[player.roomName].shift(); + console.log('after backup preset applied', backupPresets[player.roomName]); + }); } return system.applyPreset(ttsPreset) .then(() => { - player.on('transport-state', onTransportChange); + // Remove any lingering event listener before attaching a new one + player.once('transport-state', onTransportChange); return new Promise((resolve) => { announceFinished = resolve; - abortTimer = setTimeout(() => { - announceFinished = null; - logger.debug('Restoring backup preset because 30 seconds passed'); - resolve(); - }, 30000); }); }) .then(() => { clearTimeout(abortTimer); - player.removeListener('transport-state', onTransportChange); + announceFinished = undefined; + // player.removeListener('transport-state', onTransportChange); }) .then(prepareBackupPreset) .catch((err) => { logger.error(err); - player.removeListener('transport-state', onTransportChange); + // player.removeListener('transport-state', onTransportChange); return prepareBackupPreset() .then(() => { // we still want to inform that stuff broke From 8429ebc0b16aafa51a0e02bbd0cf55f82bdff383 Mon Sep 17 00:00:00 2001 From: Jimmy Shimizu Date: Sun, 29 Jan 2017 19:00:09 +0100 Subject: [PATCH 019/123] Add trace logging --- lib/helpers/all-player-announcement.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/helpers/all-player-announcement.js b/lib/helpers/all-player-announcement.js index 4a12696f..fef1e752 100644 --- a/lib/helpers/all-player-announcement.js +++ b/lib/helpers/all-player-announcement.js @@ -34,6 +34,7 @@ function saveAll(system) { }); + logger.trace('backup presets', backupPresets); return backupPresets; } @@ -139,6 +140,7 @@ function announceAll(system, uri, volume) { }) .then(() => { return backupPresets.reduce((promise, preset) => { + logger.trace('Restoring preset', preset); return promise.then(() => system.applyPreset(preset)); }, Promise.resolve()); }) From c06403bf0186d0fb3a4f8b01c179cffd9f7b94b5 Mon Sep 17 00:00:00 2001 From: Jimmy Shimizu Date: Tue, 31 Jan 2017 22:57:34 +0100 Subject: [PATCH 020/123] Should fix #445 --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 3aacaf37..34aff867 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sonos-http-api", - "version": "1.2.6", + "version": "1.2.7", "description": "A simple node app for controlling a Sonos system with basic HTTP requests", "scripts": { "start": "node server.js" @@ -17,7 +17,7 @@ "fuse.js": "^2.2.0", "node-static": "~0.7.0", "request-promise": "~1.0.2", - "sonos-discovery": "https://github.com/jishi/node-sonos-discovery/archive/v1.2.6.tar.gz" + "sonos-discovery": "https://github.com/jishi/node-sonos-discovery/archive/v1.2.7.tar.gz" }, "engines": { "node": ">=4.0.0", From 262249953992d44c2995fbb230ed0d5c2e4f8577 Mon Sep 17 00:00:00 2001 From: Jimmy Shimizu Date: Sun, 12 Feb 2017 22:38:22 +0100 Subject: [PATCH 021/123] Use JSON5 parser for settings and presets Removed "remove" grouping action, since it wasn't useful Added grouping to documentation --- README.md | 15 +++++++++++++-- lib/actions/group.js | 11 ----------- lib/actions/preset.js | 26 +++++++++++++++----------- lib/helpers/all-player-announcement.js | 6 ++++-- lib/helpers/try-load-json.js | 20 ++++++++++++++++++++ package.json | 8 ++++---- settings.js | 10 ++++------ 7 files changed, 60 insertions(+), 36 deletions(-) create mode 100644 lib/helpers/try-load-json.js diff --git a/README.md b/README.md index ffd4d887..36924662 100644 --- a/README.md +++ b/README.md @@ -95,6 +95,7 @@ The actions supported as of today: * linein (only analog linein, not PLAYBAR yet) * clip (announce custom mp3 clip) * clipall +* join / leave (Grouping actions) State ----- @@ -246,7 +247,7 @@ In the example, there is one preset called `all`, which you can apply by invokin presets folder -------------- -You can create a preset files in the presets folder with pre made presets, called presets.json. It will be loaded upon start, any changes made to files in this folder (addition, removal, modification) will trigger a reload of your presets. +You can create a preset files in the presets folder with pre made presets. It will be loaded upon start, any changes made to files in this folder (addition, removal, modification) will trigger a reload of your presets. The name of the file (xxxxxx.json) will become the name of the preset. It will be parsed as JSON5, to be more forgiving of typos. See http://json5.org/ for more info. Example content: @@ -289,7 +290,7 @@ There is an example.json bundled with this repo. The name of the file will becom settings.json ------------- -If you want to change default settings, you can create a settings.json file and put in the root folder. +If you want to change default settings, you can create a settings.json file and put in the root folder. This will be parsed as JSON5, to be more forgiving. See http://json5.org/ for more info. Available options are: @@ -666,6 +667,16 @@ Examples: *Pro-tip: announce your arrival with an epic theme song!* +Grouping +-------- + +You have basic grouping capabilities. `join` will join the selected player to the specified group (specify group by addressing any of the players in that group): + +`/Kitchen/join/Office` This will join the Kitchen player to the group that Office currently belong to. +`/Kitchen/leave` Kitchen will leave any group it was part of and become a standalone player. + +You don\t have to ungroup a player in order to join it to another group, just join it to the new group and it will jump accordingly. + Spotify and Apple Music (Experimental) ---------------------- diff --git a/lib/actions/group.js b/lib/actions/group.js index cb67bf04..62186ea2 100644 --- a/lib/actions/group.js +++ b/lib/actions/group.js @@ -21,16 +21,6 @@ function joinPlayer(player, values) { return attachTo(player, receivingPlayer.coordinator); } -function removeFromGroup(player, values) { - const leavingRoomName = decodeURIComponent(values[0]); - var leavingPlayer = player.system.getPlayer(leavingRoomName); - if(!leavingPlayer) { - logger.warn(`Room ${leavingRoomName} not found - can't remove from group of ${player.roomName}`); - return; - } - return isolate(leavingPlayer); -} - function rinconUri(player) { return `x-rincon:${player.uuid}`; } @@ -48,6 +38,5 @@ module.exports = function (api) { api.registerAction('isolate', isolate); api.registerAction('ungroup', isolate); api.registerAction('leave', isolate); - api.registerAction('remove', removeFromGroup); api.registerAction('join', joinPlayer); } diff --git a/lib/actions/preset.js b/lib/actions/preset.js index 22e158e6..c48aba3c 100644 --- a/lib/actions/preset.js +++ b/lib/actions/preset.js @@ -3,9 +3,11 @@ const fs = require('fs'); const util = require('util'); const path = require('path'); const logger = require('sonos-discovery/lib/helpers/logger'); +const tryLoadJson = require('../helpers/try-load-json'); const settings = require('../../settings'); -const presetsFilename = __dirname + '/../../presets.json'; -const presetsPath = settings.presetDir; + +const PRESETS_PATH = settings.presetDir; +const PRESETS_FILENAME = `${__dirname}/../../presets.json`; let presets = {}; function presetsAction(player, values) { @@ -46,11 +48,13 @@ function readPresetsFromDir(presets, presetPath) { return !file.stat.isDirectory() && !file.name.startsWith('.') && file.name.endsWith('.json'); }).forEach((file) => { const presetName = file.name.replace(/\.json/i, ''); - try { - presets[presetName] = JSON.parse(fs.readFileSync(file.fullPath)); - } catch (err) { - logger.warn(`could not parse preset file ${file.name} ("${err.message}"), please validate it with a JSON parser.`); - } + const preset = tryLoadJson(file.fullPath); + if (Object.keys(preset).length === 0) { + logger.warn(`could not parse preset file ${file.name}, please make sure syntax conforms with JSON5.`); + return; + } + + presets[presetName] = preset; }); } @@ -77,8 +81,8 @@ function readPresetsFromFile(presets, filename) { function initPresets() { presets = {}; - readPresetsFromFile(presets, presetsFilename); - readPresetsFromDir(presets, presetsPath); + readPresetsFromFile(presets, PRESETS_FILENAME); + readPresetsFromDir(presets, PRESETS_PATH); logger.info('Presets loaded:', util.inspect(presets, { depth: null })); @@ -88,12 +92,12 @@ module.exports = function (api) { initPresets(); let watchTimeout; try { - fs.watch(presetsPath, { persistent: false }, () => { + fs.watch(PRESETS_PATH, { persistent: false }, () => { clearTimeout(watchTimeout); watchTimeout = setTimeout(initPresets, 200); }); } catch (e) { - logger.warn(`Could not start watching dir ${presetsPath}, will not auto reload any presets. Make sure the dir exists`); + logger.warn(`Could not start watching dir ${PRESETS_PATH}, will not auto reload any presets. Make sure the dir exists`); logger.warn(e.message); } api.registerAction('preset', presetsAction); diff --git a/lib/helpers/all-player-announcement.js b/lib/helpers/all-player-announcement.js index fef1e752..263674de 100644 --- a/lib/helpers/all-player-announcement.js +++ b/lib/helpers/all-player-announcement.js @@ -35,7 +35,9 @@ function saveAll(system) { }); logger.trace('backup presets', backupPresets); - return backupPresets; + return backupPresets.sort((a,b) => { + return a.players.length > b.players.length; + }); } function announceAll(system, uri, volume) { @@ -146,7 +148,7 @@ function announceAll(system, uri, volume) { }) .catch((err) => { logger.error(err.stack); - // coordinator.removeListener('transport-state', onTransportChange); + throw err; }); } diff --git a/lib/helpers/try-load-json.js b/lib/helpers/try-load-json.js new file mode 100644 index 00000000..ff90afbf --- /dev/null +++ b/lib/helpers/try-load-json.js @@ -0,0 +1,20 @@ +const fs = require('fs'); +const JSON5 = require('json5'); +const logger = require('sonos-discovery/lib/helpers/logger'); + +function tryLoadJson(path) { + try { + const fileContent = fs.readFileSync(path); + const parsedContent = JSON5.parse(fileContent); + return parsedContent; + } catch (e) { + if (e.code === 'ENOENT') { + logger.info(`Could not find file ${path}`); + } else { + logger.warn(`Could not read file ${path}, ignoring.`, e); + } + } + return {}; +} + +module.exports = tryLoadJson; \ No newline at end of file diff --git a/package.json b/package.json index 34aff867..739ac26c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sonos-http-api", - "version": "1.2.7", + "version": "1.3.0", "description": "A simple node app for controlling a Sonos system with basic HTTP requests", "scripts": { "start": "node server.js" @@ -12,9 +12,9 @@ }, "dependencies": { "anesidora": "^1.2.0", - "aws-sdk": "^2.7.10", - "basic-auth": "~1.0.3", - "fuse.js": "^2.2.0", + "aws-sdk": "^2.12.0", + "basic-auth": "~1.1.0", + "fuse.js": "^2.5.0", "node-static": "~0.7.0", "request-promise": "~1.0.2", "sonos-discovery": "https://github.com/jishi/node-sonos-discovery/archive/v1.2.7.tar.gz" diff --git a/settings.js b/settings.js index 422072b5..7d8d3ca8 100644 --- a/settings.js +++ b/settings.js @@ -2,6 +2,7 @@ const fs = require('fs'); const path = require('path'); const logger = require('sonos-discovery/lib/helpers/logger'); +const tryLoadJson = require('./lib/helpers/try-load-json'); function merge(target, source) { Object.keys(source).forEach((key) => { @@ -23,12 +24,9 @@ var settings = { }; // load user settings -try { - const userSettings = require(path.resolve(__dirname, 'settings.json')); - merge(settings, userSettings); -} catch (e) { - logger.info('no settings file found, will only use default settings'); -} +const settingsFileFullPath = path.resolve(__dirname, 'settings.json'); +const userSettings = tryLoadJson(settingsFileFullPath); +merge(settings, userSettings); logger.debug(settings); From 420e018fcb4f7224555f243ceaf9fad87c1e8420 Mon Sep 17 00:00:00 2001 From: Jimmy Shimizu Date: Sun, 12 Feb 2017 22:43:29 +0100 Subject: [PATCH 022/123] Forgot dependency in package.json --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 739ac26c..0da95c95 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "aws-sdk": "^2.12.0", "basic-auth": "~1.1.0", "fuse.js": "^2.5.0", + "json5": "^0.5.1", "node-static": "~0.7.0", "request-promise": "~1.0.2", "sonos-discovery": "https://github.com/jishi/node-sonos-discovery/archive/v1.2.7.tar.gz" From 8c3722d1a3227f6da2bdad62ba00f49d97f8eecd Mon Sep 17 00:00:00 2001 From: Jimmy Shimizu Date: Sun, 12 Feb 2017 22:50:19 +0100 Subject: [PATCH 023/123] Sort biggest first --- lib/helpers/all-player-announcement.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/helpers/all-player-announcement.js b/lib/helpers/all-player-announcement.js index 263674de..63e89fc3 100644 --- a/lib/helpers/all-player-announcement.js +++ b/lib/helpers/all-player-announcement.js @@ -36,7 +36,7 @@ function saveAll(system) { logger.trace('backup presets', backupPresets); return backupPresets.sort((a,b) => { - return a.players.length > b.players.length; + return a.players.length < b.players.length; }); } From 16c4858e95d8d5447646b57c53680716f5dba667 Mon Sep 17 00:00:00 2001 From: Jimmy Shimizu Date: Wed, 22 Feb 2017 23:02:49 +0100 Subject: [PATCH 024/123] Add SUB actions --- README.md | 11 +++++++++ lib/actions/sub.js | 31 ++++++++++++++++++++++++++ lib/helpers/all-player-announcement.js | 2 -- package.json | 4 ++-- 4 files changed, 44 insertions(+), 4 deletions(-) create mode 100644 lib/actions/sub.js diff --git a/README.md b/README.md index 36924662..596661a8 100644 --- a/README.md +++ b/README.md @@ -96,6 +96,7 @@ The actions supported as of today: * clip (announce custom mp3 clip) * clipall * join / leave (Grouping actions) +* sub (on/off/gain/crossover/polarity) See SUB section for more info State ----- @@ -677,6 +678,16 @@ You have basic grouping capabilities. `join` will join the selected player to th You don\t have to ungroup a player in order to join it to another group, just join it to the new group and it will jump accordingly. +SUB +--- + +SUB actions include the following: +`/TV%20Room/sub/off` Turn off sub +`/TV%20Room/sub/on` Turn on sub +`/TV%20Room/sub/gain/3` Adjust gain, -15 to 15. +`/TV%20Room/sub/crossover/90` adjust crossover frequency in hz. Official values are 50 through 110 in increments of 10. Use other values at your own risk! +`/TV%20Room/sub/polarity/1` Switch "placement adjustment" or more commonly known as phase. 0 = 0°, 1 = 180° + Spotify and Apple Music (Experimental) ---------------------- diff --git a/lib/actions/sub.js b/lib/actions/sub.js new file mode 100644 index 00000000..49ef927f --- /dev/null +++ b/lib/actions/sub.js @@ -0,0 +1,31 @@ +'use strict'; + +function sub(player, values) { + if (!player.hasSub) { + return Promise.reject(new Error('This zone doesn\'t have a SUB connected')); + } + + const action = values[0]; + const value = values[1]; + + switch (action) { + case 'on': + return player.subEnable(); + case 'off': + return player.subDisable(); + case 'gain': + return player.subGain(value); + case 'crossover': + return player.subCrossover(value); + case 'polarity': + return player.subPolarity(value); + } + + return Promise.resolve({ + message: 'Valid options are on, off, gain, crossover, polarity' + }); +} + +module.exports = function (api) { + api.registerAction('sub', sub); +} \ No newline at end of file diff --git a/lib/helpers/all-player-announcement.js b/lib/helpers/all-player-announcement.js index 63e89fc3..6d350ea6 100644 --- a/lib/helpers/all-player-announcement.js +++ b/lib/helpers/all-player-announcement.js @@ -1,8 +1,6 @@ 'use strict'; const logger = require('sonos-discovery/lib/helpers/logger'); const isRadioOrLineIn = require('../helpers/is-radio-or-line-in'); -let onOneBigGroup; -let globalListenerRegistered = false; function saveAll(system) { const backupPresets = system.zones.map((zone) => { diff --git a/package.json b/package.json index 0da95c95..f68d25ca 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sonos-http-api", - "version": "1.3.0", + "version": "1.3.1", "description": "A simple node app for controlling a Sonos system with basic HTTP requests", "scripts": { "start": "node server.js" @@ -18,7 +18,7 @@ "json5": "^0.5.1", "node-static": "~0.7.0", "request-promise": "~1.0.2", - "sonos-discovery": "https://github.com/jishi/node-sonos-discovery/archive/v1.2.7.tar.gz" + "sonos-discovery": "https://github.com/jishi/node-sonos-discovery/archive/v1.3.0.tar.gz" }, "engines": { "node": ">=4.0.0", From 996d46c8600c330ebefec9978a3fe8ef2667b143 Mon Sep 17 00:00:00 2001 From: Jimmy Shimizu Date: Wed, 22 Feb 2017 23:07:05 +0100 Subject: [PATCH 025/123] Format README --- README.md | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 596661a8..0d864c41 100644 --- a/README.md +++ b/README.md @@ -673,8 +673,11 @@ Grouping You have basic grouping capabilities. `join` will join the selected player to the specified group (specify group by addressing any of the players in that group): -`/Kitchen/join/Office` This will join the Kitchen player to the group that Office currently belong to. -`/Kitchen/leave` Kitchen will leave any group it was part of and become a standalone player. +`/Kitchen/join/Office` +This will join the Kitchen player to the group that Office currently belong to. + +`/Kitchen/leave` +Kitchen will leave any group it was part of and become a standalone player. You don\t have to ungroup a player in order to join it to another group, just join it to the new group and it will jump accordingly. @@ -682,11 +685,20 @@ SUB --- SUB actions include the following: -`/TV%20Room/sub/off` Turn off sub -`/TV%20Room/sub/on` Turn on sub -`/TV%20Room/sub/gain/3` Adjust gain, -15 to 15. -`/TV%20Room/sub/crossover/90` adjust crossover frequency in hz. Official values are 50 through 110 in increments of 10. Use other values at your own risk! -`/TV%20Room/sub/polarity/1` Switch "placement adjustment" or more commonly known as phase. 0 = 0°, 1 = 180° +`/TV%20Room/sub/off` +Turn off sub + +`/TV%20Room/sub/on` +Turn on sub + +`/TV%20Room/sub/gain/3` +Adjust gain, -15 to 15. + +`/TV%20Room/sub/crossover/90` +adjust crossover frequency in hz. Official values are 50 through 110 in increments of 10. Use other values at your own risk! + +`/TV%20Room/sub/polarity/1` +Switch "placement adjustment" or more commonly known as phase. 0 = 0°, 1 = 180° Spotify and Apple Music (Experimental) ---------------------- From f7a5f5fc32e91f0029a3578b443e233787ed2ee4 Mon Sep 17 00:00:00 2001 From: Jimmy Shimizu Date: Wed, 22 Feb 2017 23:09:25 +0100 Subject: [PATCH 026/123] Format README --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 0d864c41..67d7884d 100644 --- a/README.md +++ b/README.md @@ -646,7 +646,7 @@ Optional parameter is line-in from another player. Examples: `/Office/linein` Selects line-in on zone Office belongs to, with source Office. -`Office/linein/TV Room` +`/Office/linein/TV%20Room` Selects line-in for zone Office belongs to, with source TV Room. If you want to to isolate a player and then select line-in, use the `/Office/leave` first. @@ -661,8 +661,8 @@ Like "Say" but instead of a phrase, reference a custom track from the `static/cl Examples: - clipall/sample_clip.mp3 - clipall/sample_clip.mp3/80 + /clipall/sample_clip.mp3 + /clipall/sample_clip.mp3/80 /Office/clip/sample_clip.mp3 /Office/clip/sample_clip.mp3/30 @@ -679,7 +679,7 @@ This will join the Kitchen player to the group that Office currently belong to. `/Kitchen/leave` Kitchen will leave any group it was part of and become a standalone player. -You don\t have to ungroup a player in order to join it to another group, just join it to the new group and it will jump accordingly. +You don't have to ungroup a player in order to join it to another group, just join it to the new group and it will jump accordingly. SUB --- From cb978444938f86fbc636adf16a0b72a15d7d5c1e Mon Sep 17 00:00:00 2001 From: Scott Seiber Date: Wed, 22 Feb 2017 14:11:09 -0800 Subject: [PATCH 027/123] tunein radio action (#470) * Ignore VSCode launch files, macos folder cache files * tunein radio action * Update readme for tunein action --- .gitignore | 7 +++++++ README.md | 11 +++++++++++ lib/actions/tunein.js | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+) create mode 100644 lib/actions/tunein.js diff --git a/.gitignore b/.gitignore index 03515cba..d2c2d964 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,10 @@ obj/ *.crt *.pfx *.key + +# VS +.vscode +.vscode/launch.json + +# Ignore Mac DS_Store files +.DS_Store diff --git a/README.md b/README.md index 67d7884d..686a92a1 100644 --- a/README.md +++ b/README.md @@ -755,6 +755,17 @@ Your Pandora credentials need to be added to the settings.json file ``` +Tunein +---------------------- +Given a station id this will play the streaming broadcast via the tunein service. You can find tunein station ids via services like [radiotime](http://opml.radiotime.com/) + +The following endpoint is available: + +``` +/RoomName/tunein/play/{station id} +``` + + Music Search and Play ---------------------- Perform a search for a song, artist, album or station and begin playing. Supports Apple Music, Spotify, Deezer, Deezer Elite, and your local Music Library. diff --git a/lib/actions/tunein.js b/lib/actions/tunein.js new file mode 100644 index 00000000..a6970ecd --- /dev/null +++ b/lib/actions/tunein.js @@ -0,0 +1,32 @@ +'use strict'; + +function getTuneInMetadata(uri, serviceType) { + return ` + tuneinobject.item.audioItem.audioBroadcast + SA_RINCON${serviceType}_`; +} + +function tuneIn(player, values) { + const action = values[0]; + const tuneInUri = values[1]; + const encodedTuneInUri = encodeURIComponent(tuneInUri); + const sid = player.system.getServiceId('TuneIn'); + const metadata = getTuneInMetadata(encodedTuneInUri, player.system.getServiceType('TuneIn')); + const uri = `x-sonosapi-stream:s${encodedTuneInUri}?sid=${sid}&flags=8224&sn=0`; + + if (!tuneInUri) { + return Promise.reject('Expected TuneIn station id'); + } + + if (action == 'play') { + return player.coordinator.setAVTransport(uri, metadata) + .then(() => player.coordinator.play()); + } + + return Promise.reject('TuneIn only handles the {play} action'); +} + +module.exports = function (api) { + api.registerAction('tunein', tuneIn); +} \ No newline at end of file From 68b3581ce25fa6f722a63bd7048dbbf911fbb394 Mon Sep 17 00:00:00 2001 From: Jimmy Shimizu Date: Wed, 22 Feb 2017 23:51:26 +0100 Subject: [PATCH 028/123] Limit SUB ranges to avoid damages --- README.md | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 686a92a1..87d686f1 100644 --- a/README.md +++ b/README.md @@ -692,10 +692,10 @@ Turn off sub Turn on sub `/TV%20Room/sub/gain/3` -Adjust gain, -15 to 15. +Adjust gain, -15 to 15. You can make bigger adjustments, but I'm limiting it for now because it might damage the SUB. `/TV%20Room/sub/crossover/90` -adjust crossover frequency in hz. Official values are 50 through 110 in increments of 10. Use other values at your own risk! +adjust crossover frequency in hz. Official values are 50 through 110 in increments of 10. Use other values at your own risk! My SUB gave a loud bang and shut down when setting this to high, and I thought I broke it. However, a restart woke it up again. `/TV%20Room/sub/polarity/1` Switch "placement adjustment" or more commonly known as phase. 0 = 0°, 1 = 180° diff --git a/package.json b/package.json index f68d25ca..c869eb4f 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "json5": "^0.5.1", "node-static": "~0.7.0", "request-promise": "~1.0.2", - "sonos-discovery": "https://github.com/jishi/node-sonos-discovery/archive/v1.3.0.tar.gz" + "sonos-discovery": "https://github.com/jishi/node-sonos-discovery/archive/v1.3.1.tar.gz" }, "engines": { "node": ">=4.0.0", From 7d22fa74aacad1a1f63fae939513c04049d4a4f9 Mon Sep 17 00:00:00 2001 From: Jimmy Shimizu Date: Thu, 23 Feb 2017 23:43:41 +0100 Subject: [PATCH 029/123] Identify SUBS correctly on stereo pairs sonos-discovery v1.3.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c869eb4f..650bf965 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "json5": "^0.5.1", "node-static": "~0.7.0", "request-promise": "~1.0.2", - "sonos-discovery": "https://github.com/jishi/node-sonos-discovery/archive/v1.3.1.tar.gz" + "sonos-discovery": "https://github.com/jishi/node-sonos-discovery/archive/v1.3.2.tar.gz" }, "engines": { "node": ">=4.0.0", From a1e4698355587c474c58fb1edef244497e559d3c Mon Sep 17 00:00:00 2001 From: Jimmy Shimizu Date: Sun, 26 Feb 2017 23:00:26 +0100 Subject: [PATCH 030/123] Add NightMode and SpeechEnhancement endpoints Identify SUBs correctly on PLAYBARs --- README.md | 3 +++ lib/actions/equalizer.js | 16 ++++++++++++++++ package.json | 4 ++-- 3 files changed, 21 insertions(+), 2 deletions(-) create mode 100644 lib/actions/equalizer.js diff --git a/README.md b/README.md index 87d686f1..78eb5d0c 100644 --- a/README.md +++ b/README.md @@ -97,6 +97,9 @@ The actions supported as of today: * clipall * join / leave (Grouping actions) * sub (on/off/gain/crossover/polarity) See SUB section for more info +* nightmode (on/off, PLAYBAR only) +* speechenhancement (on/off, PLAYBAR only) + State ----- diff --git a/lib/actions/equalizer.js b/lib/actions/equalizer.js new file mode 100644 index 00000000..ca1157d7 --- /dev/null +++ b/lib/actions/equalizer.js @@ -0,0 +1,16 @@ +'use strict'; + +function nightMode(player, values) { + const enable = values[0] === 'on'; + return player.nightMode(enable); +} + +function speechEnhancement(player, values) { + const enable = values[0] === 'on'; + return player.speechEnhancement(enable); +} + +module.exports = function (api) { + api.registerAction('nightmode', nightMode); + api.registerAction('speechenhancement', speechEnhancement); +} diff --git a/package.json b/package.json index 650bf965..9cf0645e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sonos-http-api", - "version": "1.3.1", + "version": "1.4.0", "description": "A simple node app for controlling a Sonos system with basic HTTP requests", "scripts": { "start": "node server.js" @@ -18,7 +18,7 @@ "json5": "^0.5.1", "node-static": "~0.7.0", "request-promise": "~1.0.2", - "sonos-discovery": "https://github.com/jishi/node-sonos-discovery/archive/v1.3.2.tar.gz" + "sonos-discovery": "https://github.com/jishi/node-sonos-discovery/archive/v1.4.0.tar.gz" }, "engines": { "node": ">=4.0.0", From a1817a1194ac24f8ef6d6551959cdcbfcdad9a23 Mon Sep 17 00:00:00 2001 From: Jimmy Shimizu Date: Tue, 14 Mar 2017 23:20:51 +0100 Subject: [PATCH 031/123] Add equalizer info to state response resolves #472 Use discovery v1.4.1 --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 9cf0645e..19907b34 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sonos-http-api", - "version": "1.4.0", + "version": "1.4.1", "description": "A simple node app for controlling a Sonos system with basic HTTP requests", "scripts": { "start": "node server.js" @@ -18,7 +18,7 @@ "json5": "^0.5.1", "node-static": "~0.7.0", "request-promise": "~1.0.2", - "sonos-discovery": "https://github.com/jishi/node-sonos-discovery/archive/v1.4.0.tar.gz" + "sonos-discovery": "https://github.com/jishi/node-sonos-discovery/archive/v1.4.1.tar.gz" }, "engines": { "node": ">=4.0.0", From 8067ca069a5e53fd6d336760058bd564b508879b Mon Sep 17 00:00:00 2001 From: Johan Godfried Date: Sun, 7 May 2017 21:12:44 +0200 Subject: [PATCH 032/123] Changed function invokeWebhook to allow for additional settings (#487) * Changed function invokeWebhook to allow for additional settings The invokeWebhook function was changed so that the following settings can be used in the settings.json file: webhook_type - set the name of the entity that contains the type (defaults to type) webhook_data - set the name of the entity that contains the data (defaults to data) webhook_header_name - add a header (for instance for authorization) (defaults to none) webhook_header_contents - set the contents of the additional header. The reason for this change is that I want to point this webhook to Splunk. Splunk requires an event entity in the payload in stead of a data entity and the type entity is unknown as well. In addition, authentication is done through the header "Authentication". I have programmed it such that it is backwards compatible with the previous version. * Changed new property and variable names to camelCase --- lib/sonos-http-api.js | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/lib/sonos-http-api.js b/lib/sonos-http-api.js index 2bf2e885..6f776965 100644 --- a/lib/sonos-http-api.js +++ b/lib/sonos-http-api.js @@ -113,22 +113,32 @@ function HttpAPI(discovery, settings) { } function invokeWebhook(type, data) { + var typeName = "type"; + var dataName = "data"; if (!settings.webhook) return; + if (settings.webhookType) { typeName = settings.webhookType; } + if (settings.webhookData) { dataName = settings.webhookData; } + const jsonBody = JSON.stringify({ - type: type, - data: data + [typeName]: type, + [dataName]: data }); const body = new Buffer(jsonBody, 'utf8'); + var headers = { + 'Content-Type': 'application/json', + 'Content-Length': body.length + } + if (settings.webhookHeaderName && settings.webhookHeaderContents) { + headers[settings.webhookHeaderName] = settings.webhookHeaderContents; + } + request({ method: 'POST', uri: settings.webhook, - headers: { - 'Content-Type': 'application/json', - 'Content-Length': body.length - }, + headers: headers, body }) .catch(function (err) { From 65296b792ea9451db354b4a3915e3c7a04e2de0a Mon Sep 17 00:00:00 2001 From: jplourde5 Date: Mon, 29 May 2017 03:30:31 -0400 Subject: [PATCH 033/123] Fixed Apple Radio (#507) The original search URI for the stations quit working and now returns empty results. Determined how to fix it using the iTunes search URI instead. --- lib/music_services/appleDef.js | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/lib/music_services/appleDef.js b/lib/music_services/appleDef.js index 1b6a621a..d26aace1 100644 --- a/lib/music_services/appleDef.js +++ b/lib/music_services/appleDef.js @@ -4,12 +4,12 @@ const appleDef = { search: { album: 'https://itunes.apple.com/search?media=music&limit=1&entity=album&attribute=albumTerm&term=', song: 'https://itunes.apple.com/search?media=music&limit=50&entity=song&term=', - station: 'https://sticky-summer-lb.inkstone-clients.net/api/v1/searchMusic?limit=1&media=appleMusic&entity=station&term=' + station: 'https://itunes.apple.com/search?media=music&limit=50&entity=musicArtist&term=' }, metastart: { album: '0004206calbum%3a', song: '00032020song%3a', - station: '000c206cradio%3a' + station: '000c206cradio%3ara.' }, parent: { album: '00020000album:', @@ -38,7 +38,7 @@ function getURI(type, id) { return `x-sonos-http:song%3a${id}.mp4?sid=${sid}&flags=8224&sn=${accountSN}`; } else if (type == 'station') { - return `x-sonosapi-radio:radio%3a${id}?sid=${sid}&flags=8300&sn=${accountSN}`; + return `x-sonosapi-radio:radio%3ara.${id}?sid=${sid}&flags=8300&sn=${accountSN}`; } } @@ -85,7 +85,9 @@ function getMetadata(type, id, name, title) { const parentUri = appleDef.parent[type] + name; const objectType = appleDef.object[type]; - if (type != 'station') { + if (type == 'station') { + title = title + ' Radio'; + } else { title = ''; } @@ -106,8 +108,17 @@ function getURIandMetadata(type, resList) metadata: '' }; - Id = (type=='album')?resList.results[0].collectionId:resList.results[0].id; - Title = (type=='album')?resList.results[0].collectionName:resList.results[0].name; + if (type=='album') { + Id = resList.results[0].collectionId; + Title = resList.results[0].collectionName; + } else + if (type=='station') { + Id = resList.results[0].artistId; + Title = resList.results[0].artistName; + } else { + Id = resList.results[0].id; + Title = resList.results[0].name; + } Name = Title.toLowerCase().replace(' radio','').replace('radio ','').replace("'","'"); MetadataID = appleDef.metastart[type] + encodeURIComponent(Id); From 9288165e36e4b52b6f238772f14761589819660b Mon Sep 17 00:00:00 2001 From: jplourde5 Date: Mon, 29 May 2017 03:31:05 -0400 Subject: [PATCH 034/123] =?UTF-8?q?Fixes=20problem=20with=20issuing=20play?= =?UTF-8?q?=20command=20on=20a=20restarted=20Sonos=20player=20w=E2=80=A6?= =?UTF-8?q?=20(#506)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fixes problem with issuing play command on a restarted Sonos player with an empty status * Fixes problem with Play on restarted Sonos player --- lib/actions/musicSearch.js | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/lib/actions/musicSearch.js b/lib/actions/musicSearch.js index 68c13e83..4e6e9f61 100644 --- a/lib/actions/musicSearch.js +++ b/lib/actions/musicSearch.js @@ -222,11 +222,7 @@ function musicSearch(player, values) { UaM = serviceDef.urimeta(type, resList); return player.coordinator.clearQueue() - .then(() => { - if (isRadioOrLineIn(player.coordinator.avTransportUri)) { - return player.coordinator.setAVTransport(queueURI, ''); - } - }) + .then(() => player.coordinator.setAVTransport(queueURI, '')) .then(() => player.coordinator.addURIToQueue(UaM.uri, UaM.metadata, true, 1)) .then(() => player.coordinator.play()); } else { // Play songs @@ -237,11 +233,7 @@ function musicSearch(player, values) { } else { if (tracks.isArtist) { // Play numerous songs by the specified artist return player.coordinator.clearQueue() - .then(() => { - if (isRadioOrLineIn(player.coordinator.avTransportUri)) { - return player.coordinator.setAVTransport(queueURI, ''); - } - }) + .then(() => player.coordinator.setAVTransport(queueURI, '')) .then(() => player.coordinator.addURIToQueue(tracks.queueTracks[0].uri, tracks.queueTracks[0].metadata, true, 1)) .then(() => player.coordinator.play()) .then(() => { @@ -260,11 +252,7 @@ function musicSearch(player, values) { nextTrackNo = (empty) ? 1 : player.coordinator.state.trackNo + 1; }) .then(() => player.coordinator.addURIToQueue(tracks.queueTracks[0].uri, tracks.queueTracks[0].metadata, true, nextTrackNo)) - .then(() => { - if (isRadioOrLineIn(player.coordinator.state.currentTrack.uri)) { - return player.coordinator.setAVTransport(queueURI, ''); - } - }) + .then(() => player.coordinator.setAVTransport(queueURI, '')) .then(() => { if (!empty) { return player.coordinator.nextTrack(); From ffdd2e2e8caf58359ceab96a0c594ecc7a099871 Mon Sep 17 00:00:00 2001 From: Petter Samuelsen Date: Mon, 29 May 2017 09:31:23 +0200 Subject: [PATCH 035/123] Playlist music search for Spotify (#498) * Don't shuffle tracks after search * Search for playlist * Update readme with playlist info --- README.md | 39 +++++++++++++++++--------------- lib/actions/musicSearch.js | 7 ++---- lib/music_services/spotifyDef.js | 23 +++++++++++++++---- 3 files changed, 41 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 78eb5d0c..7f5d8b68 100644 --- a/README.md +++ b/README.md @@ -332,9 +332,9 @@ Example: "username": "your-pandora-account-email-address", "password": "your-pandora-password" }, - "library": { - "randomQueueLimit": 50 - } + "library": { + "randomQueueLimit": 50 + } } ``` @@ -706,7 +706,7 @@ Switch "placement adjustment" or more commonly known as phase. 0 = 0°, 1 = 180 Spotify and Apple Music (Experimental) ---------------------- -Allows you to perform your own external searches for Apple Music or Spotify songs or albums and play a specified song or track ID. The Music Search funtionality outlined further below performs a search of its own and plays the specified music. +Allows you to perform your own external searches for Apple Music or Spotify songs or albums and play a specified song or track ID. The Music Search funtionality outlined further below performs a search of its own and plays the specified music. The following endpoints are available: @@ -724,7 +724,7 @@ The following endpoints are available: You can find Apple Music song and album IDs via the [iTunes Search API](https://affiliate.itunes.apple.com/resources/documentation/itunes-store-web-service-search-api/). -It only handles a single spotify account currently. It will probably use the first account added on your system. +It only handles a single spotify account currently. It will probably use the first account added on your system. SiriusXM @@ -738,14 +738,14 @@ You can specify a SiriusXM channel number or station name and the station will b Pandora ---------------------- -Perform a search for one of your Pandora stations and begin playing. Give the currently playing song a thumbs up or thumbs down. Requires a valid Pandora account and credentials. +Perform a search for one of your Pandora stations and begin playing. Give the currently playing song a thumbs up or thumbs down. Requires a valid Pandora account and credentials. The following endpoints are available: ``` /RoomName/pandora/play/{station name} Plays the closest match to the specified station name in your list of Pandora stations /RoomName/pandora/thumbsup Gives the current playing Pandora song a thumbs up -/RoomName/pandora/thumbsdown Gives the current playing Pandora song a thumbs down +/RoomName/pandora/thumbsdown Gives the current playing Pandora song a thumbs down ``` Your Pandora credentials need to be added to the settings.json file @@ -756,7 +756,7 @@ Your Pandora credentials need to be added to the settings.json file "password": "your-pandora-password" } ``` - + Tunein ---------------------- @@ -767,11 +767,11 @@ The following endpoint is available: ``` /RoomName/tunein/play/{station id} ``` - + Music Search and Play ---------------------- -Perform a search for a song, artist, album or station and begin playing. Supports Apple Music, Spotify, Deezer, Deezer Elite, and your local Music Library. +Perform a search for a song, artist, album or station and begin playing. Supports Apple Music, Spotify, Deezer, Deezer Elite, and your local Music Library. The following endpoint is available: @@ -780,13 +780,13 @@ The following endpoint is available: Service options: apple, spotify, deezer, elite, library -Type options for apple, spotify, deezer, and elite: album, song, station -Station plays a Pandora like artist radio station for a specified artist name. +Type options for apple, spotify, deezer, and elite: album, song, station, playlist +Station plays a Pandora like artist radio station for a specified artist name. Apple Music also supports song titles and artist name + song title. -Type options for library: album, song, load -Load performs an initial load or relaod of the local Sonos music library. -The music library will also get loaded the first time that the library service is +Type options for library: album, song, load +Load performs an initial load or relaod of the local Sonos music library. +The music library will also get loaded the first time that the library service is used if the load command has not been issued before. Search terms for song for all services: artist name, song title, artist name + song title @@ -797,8 +797,8 @@ Search terms for station for spotify and deezer: artist name Search terms for station for library: not supported Specifying just an artist name will load the queue with up to 50 of the artist's most popular songs -Specifying a song title or artist + song title will insert the closest match to the song into -the queue and start playing it. More than 50 tracks can be loaded from the local library by using +Specifying a song title or artist + song title will insert the closest match to the song into +the queue and start playing it. More than 50 tracks can be loaded from the local library by using library.randomQueueLimit in the settings.json file to set the maximum to a higher value. Examples: @@ -810,6 +810,9 @@ Examples: /Playroom/musicsearch/library/album/red+hot+chili+peppers+the+getaway /Kitchen/musicsearch/spotify/album/dark+necessities +/Kitchen/musicsearch/spotify/playlist/morning+acoustic +/Kitchen/musicsearch/spotify/playlist/dinner+with+friends + /Den/musicsearch/spotify/station/red+hot+chili+peppers /Kitchen/musicsearch/apple/station/dark+necessities (Only Apple Music supports song titles) /Playroom/musicsearch/apple/station/red+hot+chili+peppers+dark+necessities (Only Apple Music supports song titles) @@ -881,4 +884,4 @@ or DOCKER ----- -Docker usage is maintained by [Chris Nesbitt-Smith](https://github.com/chrisns) at [chrisns/docker-node-sonos-http-api](https://github.com/chrisns/docker-node-sonos-http-api) \ No newline at end of file +Docker usage is maintained by [Chris Nesbitt-Smith](https://github.com/chrisns) at [chrisns/docker-node-sonos-http-api](https://github.com/chrisns/docker-node-sonos-http-api) diff --git a/lib/actions/musicSearch.js b/lib/actions/musicSearch.js index 4e6e9f61..4e34ee68 100644 --- a/lib/actions/musicSearch.js +++ b/lib/actions/musicSearch.js @@ -11,7 +11,7 @@ const libraryDef = require('../music_services/libraryDef'); const musicServices = ['apple','spotify','deezer','elite','library']; const serviceNames = {apple:'Apple Music',spotify:'Spotify',deezer:'Deezer',elite:'Deezer',library:'Library'}; -const musicTypes = ['album','song','station','load']; +const musicTypes = ['album','song','station','load','playlist']; var country = ''; var accountId = ''; @@ -172,9 +172,6 @@ function loadTracks(service, type, tracksJson) } else { tracks.isArtist = (searchType == 2); } - if (tracks.isArtist) { - tracks.queueTracks.shuffle(); - } } return tracks; @@ -218,7 +215,7 @@ function musicSearch(player, values) { return player.coordinator.setAVTransport(UaM.uri, UaM.metadata) .then(() => player.coordinator.play()); } else - if ((type == 'album') && (service != 'library')) { + if ((type == 'album' || type =='playlist') && (service != 'library')) { UaM = serviceDef.urimeta(type, resList); return player.coordinator.clearQueue() diff --git a/lib/music_services/spotifyDef.js b/lib/music_services/spotifyDef.js index 0289ba16..4ca72ae3 100644 --- a/lib/music_services/spotifyDef.js +++ b/lib/music_services/spotifyDef.js @@ -4,22 +4,26 @@ const spotifyDef = { search: { album: 'https://api.spotify.com/v1/search?type=album&limit=1&q=album:', song: 'https://api.spotify.com/v1/search?type=track&limit=50&q=', - station: 'https://api.spotify.com/v1/search?type=artist&limit=1&q=' + station: 'https://api.spotify.com/v1/search?type=artist&limit=1&q=', + playlist: 'https://api.spotify.com/v1/search?type=playlist&q=' }, metastart: { album: '0004206cspotify%3aalbum%3a', song: '00032020spotify%3atrack%3a', - station: '000c206cspotify:artistRadio%3a' + station: '000c206cspotify:artistRadio%3a', + playlist: '0004206cspotify%3aplaylist%3a' }, parent: { album: '00020000album:', song: '00020000track:', - station: '00052064spotify%3aartist%3a' + station: '00052064spotify%3aartist%3a', + playlist:'00020000playlist:', }, object: { album: 'container.album.musicAlbum', song: 'item.audioItem.musicTrack', - station: 'item.audioItem.audioBroadcast.#artistRadio' + station: 'item.audioItem.audioBroadcast.#artistRadio', + playlist:'container.playlistContainer', }, service: setService, @@ -39,6 +43,9 @@ function getURI(type, id) { } else if (type == 'station') { return `x-sonosapi-radio:spotify%3aartistRadio%3a${id}?sid=${sid}&flags=8300&sn=${accountSN}`; + } else + if (type == 'playlist') { + return `x-rincon-cpcontainer:0006206c${id}`; } } @@ -112,6 +119,9 @@ function getURIandMetadata(type, resList) } else if (type == 'station') { items = resList.artists.items; + } else + if (type == 'playlist') { + items = resList.playlists.items; } Id = items[0].id; @@ -119,7 +129,7 @@ function getURIandMetadata(type, resList) Name = Title.toLowerCase().replace(' radio','').replace('radio ',''); MetadataID = spotifyDef.metastart[type] + encodeURIComponent(Id); - UaM.metadata = getMetadata(type, MetadataID, (type=='album')?Title.toLowerCase():Id, Title); + UaM.metadata = getMetadata(type, MetadataID, (type=='album' || type=='playlist')?Title.toLowerCase() : Id, Title); UaM.uri = getURI(type, encodeURIComponent((type=='station')?items[0].id:items[0].uri)); return UaM; @@ -171,6 +181,9 @@ function isEmpty(type, resList) } else if (type == 'station') { count = resList.artists.items.length; + } else + if (type == 'playlist') { + count = resList.playlists.items.length; } return (count == 0); From f44283359003ad55f40d6524150a4c2f38177407 Mon Sep 17 00:00:00 2001 From: jplourde5 Date: Mon, 12 Jun 2017 14:22:13 -0400 Subject: [PATCH 036/123] Added Spotify Authentication to MusicSearch (#514) * Update musicSearch.js * Update appleDef.js * Update deezerDef.js * Update appleDef.js * Update libraryDef.js * Update spotifyDef.js * Update README.md * Update README.md * Update spotifyDef.js * Update musicSearch.js --- README.md | 8 ++++ lib/actions/musicSearch.js | 31 ++++++++---- lib/music_services/appleDef.js | 12 ++++- lib/music_services/deezerDef.js | 14 +++++- lib/music_services/libraryDef.js | 11 ++++- lib/music_services/spotifyDef.js | 82 +++++++++++++++++++++++++++++++- 6 files changed, 143 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 7f5d8b68..8dab7db3 100644 --- a/README.md +++ b/README.md @@ -332,6 +332,10 @@ Example: "username": "your-pandora-account-email-address", "password": "your-pandora-password" }, + "spotify": { + "clientId": "your-spotify-application-clientId", + "clientSecret": "your-spotify-application-clientSecret" + }, "library": { "randomQueueLimit": 50 } @@ -340,6 +344,10 @@ Example: Override as it suits you. +Note for Spotify users! +----------------------- + +To use Spotify, go to https://developer.spotify.com/my-applications/#!/applications/create and create a Spotify application to get your client keys. You can name it Sonos or anything else and you don't have to change any values. Use the Client ID and the Client Secret values in the settings.json file as indicated above. Favorites diff --git a/lib/actions/musicSearch.js b/lib/actions/musicSearch.js index 4e34ee68..fb33d9bd 100644 --- a/lib/actions/musicSearch.js +++ b/lib/actions/musicSearch.js @@ -62,11 +62,20 @@ function getAccountId(player, service) } } +function getRequestOptions(serviceDef, url) { + const headers = serviceDef.headers(); + return { + url: url, + json: true, + headers: headers, + } +}; function doSearch(service, type, term) { var serviceDef = getService(service); var url = serviceDef.search[type]; + var authenticate = serviceDef.authenticate; term = decodeURIComponent(term); @@ -114,14 +123,14 @@ function doSearch(service, type, term) .then((res) => { country = res.country; url += serviceDef.country + country; - return request({url: url, json: true}); + return authenticate().then(() => request(getRequestOptions(serviceDef, url))); }); } else { if (serviceDef.country != '') { url += serviceDef.country + country; } - return request({url: url, json: true}); + return authenticate().then(() => request(getRequestOptions(serviceDef, url))); } } @@ -134,7 +143,7 @@ Array.prototype.shuffle=function(){ return this; } -function loadTracks(service, type, tracksJson) +function loadTracks(player, service, type, tracksJson) { var tracks = getService(service).tracks(type, tracksJson); @@ -173,7 +182,12 @@ function loadTracks(service, type, tracksJson) tracks.isArtist = (searchType == 2); } } - + + //To avoid playing the same song first in a list of artist tracks when shuffle is on + if (tracks.isArtist && player.coordinator.state.playMode.shuffle) { + tracks.queueTracks.shuffle(); + } + return tracks; } @@ -183,8 +197,7 @@ function musicSearch(player, values) { const type = values[1]; const term = values[2]; const queueURI = 'x-rincon-queue:' + player.uuid + '#0'; - var serviceDef = null; - + if (musicServices.indexOf(service) == -1) { return Promise.reject('Invalid music service'); } @@ -199,11 +212,11 @@ function musicSearch(player, values) { return getAccountId(player, service) .then(() => { - serviceDef = getService(service); - serviceDef.service(player, accountId, accountSN, country); return doSearch(service, type, term); }) .then((resList) => { + const serviceDef = getService(service); + serviceDef.service(player, accountId, accountSN, country); if (serviceDef.empty(type, resList)) { return Promise.reject('No matches were found'); } else { @@ -223,7 +236,7 @@ function musicSearch(player, values) { .then(() => player.coordinator.addURIToQueue(UaM.uri, UaM.metadata, true, 1)) .then(() => player.coordinator.play()); } else { // Play songs - var tracks = loadTracks(service, type, resList); + var tracks = loadTracks(player, service, type, resList); if (tracks.count == 0) { return Promise.reject('No matches were found'); diff --git a/lib/music_services/appleDef.js b/lib/music_services/appleDef.js index d26aace1..52df8891 100644 --- a/lib/music_services/appleDef.js +++ b/lib/music_services/appleDef.js @@ -27,9 +27,19 @@ const appleDef = { tracks: loadTracks, empty: isEmpty, metadata: getMetadata, - urimeta: getURIandMetadata + urimeta: getURIandMetadata, + headers: getTokenHeaders, + authenticate: authenticateService } +function getTokenHeaders() { + return null; +}; + +function authenticateService() { + return Promise.resolve(); +} + function getURI(type, id) { if (type == 'album') { return `x-rincon-cpcontainer:0004206calbum%3a${id}`; diff --git a/lib/music_services/deezerDef.js b/lib/music_services/deezerDef.js index 49779fd8..db1d79e0 100644 --- a/lib/music_services/deezerDef.js +++ b/lib/music_services/deezerDef.js @@ -28,8 +28,18 @@ const deezerDef = { tracks: loadTracks, empty: isEmpty, metadata: getMetadata, - urimeta: getURIandMetadata -} + urimeta: getURIandMetadata, + headers: getTokenHeaders, + authenticate: authenticateService +} + +function getTokenHeaders() { + return null; +} + +function authenticateService() { + return Promise.resolve(); +} function getURI(type, id) { if (type == 'album') { diff --git a/lib/music_services/libraryDef.js b/lib/music_services/libraryDef.js index 8c7d62b2..a12c0d2e 100644 --- a/lib/music_services/libraryDef.js +++ b/lib/music_services/libraryDef.js @@ -49,9 +49,18 @@ const libraryDef = { searchlib: searchLibrary, empty: isEmpty, metadata: getMetadata, - urimeta: getURIandMetadata + urimeta: getURIandMetadata, + headers: getTokenHeaders, + authenticate: authenticateService } +function getTokenHeaders() { + return null; +} + +function authenticateService() { + return Promise.resolve(); +} function setService(player, p_accountId, p_accountSN, p_country) { } diff --git a/lib/music_services/spotifyDef.js b/lib/music_services/spotifyDef.js index 4ca72ae3..fe469d8e 100644 --- a/lib/music_services/spotifyDef.js +++ b/lib/music_services/spotifyDef.js @@ -1,4 +1,19 @@ 'use strict'; + +var request = require('request-promise'); +const settings = require('../../settings'); + +var clientId = ""; +var clientSecret = ""; + +try { + clientId = settings.spotify.clientId; + clientSecret = settings.spotify.clientSecret; +} catch(e) { +} + +var clientToken = null; + const spotifyDef = { country: '&market=', search: { @@ -31,8 +46,71 @@ const spotifyDef = { tracks: loadTracks, empty: isEmpty, metadata: getMetadata, - urimeta: getURIandMetadata -} + urimeta: getURIandMetadata, + headers: getTokenHeaders, + authenticate: authenticateService, +} + +var toBase64 = (string) => new Buffer(string).toString('base64'); + +const SPOTIFY_TOKEN_URL = 'https://accounts.spotify.com/api/token'; + +const mapResponse = ({ access_token, token_type, expires_in }) => ({ + accessToken: access_token, + tokenType: token_type, + expiresIn: expires_in, +}); + +const getHeaders = () => { + const authString = `${clientId}:${clientSecret}`; + return { + Authorization: `Basic ${toBase64(authString)}`, + 'Content-Type': 'application/x-www-form-urlencoded', + }; +}; + +const getOptions = (url) => { + return { + url, + headers: getHeaders(), + json: true, + method: 'POST', + form: { + grant_type: 'client_credentials', + }, + }; +}; + +const auth = () => { + const options = getOptions(SPOTIFY_TOKEN_URL); + return new Promise((resolve, reject) => { + request(options).then((response) => { + const responseMapped = mapResponse(response); + resolve(responseMapped); + }).catch((err) => { + reject(new Error(`Unable to authenticate Spotify with client id: ${clientId}`)); + }) + }); +}; + +function getTokenHeaders() { + if (clientToken == null) { + return null; + } + return { + Authorization: `Bearer ${clientToken}` + }; +} + +function authenticateService() { + return new Promise((resolve, reject) => { + auth().then((response) => { + const { accessToken } = response; + clientToken = accessToken; + resolve(); + }).catch(reject); + }); +} function getURI(type, id) { if (type == 'album') { From fd2d861763169670b762cc254a65cdc8f13741b3 Mon Sep 17 00:00:00 2001 From: Jimmy Shimizu Date: Mon, 12 Jun 2017 21:11:34 +0200 Subject: [PATCH 037/123] Create README.md --- README.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/README.md b/README.md index 8dab7db3..82c3aa58 100644 --- a/README.md +++ b/README.md @@ -893,3 +893,22 @@ DOCKER ----- Docker usage is maintained by [Chris Nesbitt-Smith](https://github.com/chrisns) at [chrisns/docker-node-sonos-http-api](https://github.com/chrisns/docker-node-sonos-http-api) + +## FIREWALL + +If you are running this in an environment where you manually have to unblock traffic to and from the machine, the following traffic needs to be allowed: + +### Incoming +TCP, port 3500 (Sonos events) +UDP, port 1905 (Sonos initial discovery) +TCP, port 5005 (if using the default api port) +TCP, port 5006 (if using https support, optional) + +### Outgoing +TCP, port 1400 (Sonos control commands) +UDP, port 1900 (Sonos initial discovery) +TCP, whatever port used for webhooks (optional) + +The UDP traffic is a mixture of multicast (outgoing), broadcast (outgoing) and unicast (incoming). The multicast address is 239.255.255.250, the broadcast is 255.255.255.255 and the unicast is from the Sonos players. + +If port 3500 is occupied while trying to bind it, it will try using 3501, 3502, 3503 etc. You would need to adjust your firewall rules accordingly, if running multiple instances of this software, or any other software utilizing these ports. From fcb5c34e97c1736b87abae20b3e2eda326fe70c2 Mon Sep 17 00:00:00 2001 From: Jimmy Shimizu Date: Mon, 12 Jun 2017 21:13:31 +0200 Subject: [PATCH 038/123] Create README.md --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 82c3aa58..d7e8c082 100644 --- a/README.md +++ b/README.md @@ -899,15 +899,19 @@ Docker usage is maintained by [Chris Nesbitt-Smith](https://github.com/chrisns) If you are running this in an environment where you manually have to unblock traffic to and from the machine, the following traffic needs to be allowed: ### Incoming +``` TCP, port 3500 (Sonos events) UDP, port 1905 (Sonos initial discovery) TCP, port 5005 (if using the default api port) TCP, port 5006 (if using https support, optional) - +``` ### Outgoing +``` TCP, port 1400 (Sonos control commands) UDP, port 1900 (Sonos initial discovery) TCP, whatever port used for webhooks (optional) +TCP, port 80/443 (for looking up hig res cover arts on various music services) +``` The UDP traffic is a mixture of multicast (outgoing), broadcast (outgoing) and unicast (incoming). The multicast address is 239.255.255.250, the broadcast is 255.255.255.255 and the unicast is from the Sonos players. From f6d725230ed71a0c34fd8f548c994deb6b269d4c Mon Sep 17 00:00:00 2001 From: Jimmy Shimizu Date: Mon, 19 Jun 2017 21:57:02 +0200 Subject: [PATCH 039/123] Fix Node 4 compatibility. Fix Spotify queue bug --- lib/actions/musicSearch.js | 2 +- lib/music_services/spotifyDef.js | 17 ++++++++++------- package.json | 2 +- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/lib/actions/musicSearch.js b/lib/actions/musicSearch.js index fb33d9bd..5625284c 100644 --- a/lib/actions/musicSearch.js +++ b/lib/actions/musicSearch.js @@ -196,7 +196,7 @@ function musicSearch(player, values) { const service = values[0]; const type = values[1]; const term = values[2]; - const queueURI = 'x-rincon-queue:' + player.uuid + '#0'; + const queueURI = 'x-rincon-queue:' + player.coordinator.uuid + '#0'; if (musicServices.indexOf(service) == -1) { return Promise.reject('Invalid music service'); diff --git a/lib/music_services/spotifyDef.js b/lib/music_services/spotifyDef.js index fe469d8e..33036fcc 100644 --- a/lib/music_services/spotifyDef.js +++ b/lib/music_services/spotifyDef.js @@ -6,10 +6,9 @@ const settings = require('../../settings'); var clientId = ""; var clientSecret = ""; -try { +if (settings.spotify) { clientId = settings.spotify.clientId; clientSecret = settings.spotify.clientSecret; -} catch(e) { } var clientToken = null; @@ -55,13 +54,17 @@ var toBase64 = (string) => new Buffer(string).toString('base64'); const SPOTIFY_TOKEN_URL = 'https://accounts.spotify.com/api/token'; -const mapResponse = ({ access_token, token_type, expires_in }) => ({ - accessToken: access_token, - tokenType: token_type, - expiresIn: expires_in, +const mapResponse = (response) => ({ + accessToken: response.access_token, + tokenType: response.token_type, + expiresIn: response.expires_in, }); const getHeaders = () => { + console.log('spotify', clientId, clientSecret) + if (!clientId || !clientSecret) { + throw new Error('You are missing spotify clientId and secret in settings.json! Please read the README for instructions on how to generate and add them'); + } const authString = `${clientId}:${clientSecret}`; return { Authorization: `Basic ${toBase64(authString)}`, @@ -105,7 +108,7 @@ function getTokenHeaders() { function authenticateService() { return new Promise((resolve, reject) => { auth().then((response) => { - const { accessToken } = response; + const accessToken = response.accessToken; clientToken = accessToken; resolve(); }).catch(reject); diff --git a/package.json b/package.json index 19907b34..d45e09a8 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "json5": "^0.5.1", "node-static": "~0.7.0", "request-promise": "~1.0.2", - "sonos-discovery": "https://github.com/jishi/node-sonos-discovery/archive/v1.4.1.tar.gz" + "sonos-discovery": "https://github.com/jishi/node-sonos-discovery/archive/v1.5.1.tar.gz" }, "engines": { "node": ">=4.0.0", From 148bc518435f9519a1fac133073f7f27c3f87e17 Mon Sep 17 00:00:00 2001 From: Jimmy Shimizu Date: Mon, 19 Jun 2017 20:49:43 +0200 Subject: [PATCH 040/123] Allow limit when fetching queue Fixes #493 --- README.md | 12 ++++++++---- lib/actions/queue.js | 14 ++++++++++++-- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index d7e8c082..295d5651 100644 --- a/README.md +++ b/README.md @@ -140,22 +140,26 @@ Example of a state json: Queue ----- Obtain the current queue list from a specified player. The request will accept: - - No parameters + - limit (optional) + - offset (optional, requires limit) + - detailed flag (optional, include uri in response) - `http://localhost:5005/living room/queue` + http://localhost:5005/living room/queue + http://localhost:5005/living room/queue/10 (only return top 10) + http://localhost:5005/living room/queue/10/10 (return result 11-20) + http://localhost:5005/living room/queue/detailed + http://localhost:5005/living room/queue/10/detailed Example queue response: ``` [ { - "uri": "x-sonos-spotify:spotify%3atrack%3a0AvV49z4EPz5ocYN7eKGAK?sid=9&flags=8224&sn=3", "albumArtURI": "/getaa?s=1&u=x-sonos-spotify%3aspotify%253atrack%253a0AvV49z4EPz5ocYN7eKGAK%3fsid%3d9%26flags%3d8224%26sn%3d3", "title": "No Diggity", "artist": "Blackstreet", "album": "Another Level" }, { - "uri": "x-sonos-spotify:spotify%3atrack%3a5OQGeJ1ceykovrykZsGhqL?sid=9&flags=8224&sn=3", "albumArtURI": "/getaa?s=1&u=x-sonos-spotify%3aspotify%253atrack%253a5OQGeJ1ceykovrykZsGhqL%3fsid%3d9%26flags%3d8224%26sn%3d3", "title": "Breathless", "artist": "The Corrs", diff --git a/lib/actions/queue.js b/lib/actions/queue.js index 3f48fad1..4f23e020 100644 --- a/lib/actions/queue.js +++ b/lib/actions/queue.js @@ -13,9 +13,19 @@ function simplify(items) { } function queue(player, values) { - const detailed = values[0] === 'detailed'; + const detailed = values[values.length - 1] === 'detailed'; + let limit; + let offset; - const promise = player.coordinator.getQueue(); + if (/\d+/.test(values[0])) { + limit = parseInt(values[0]); + } + + if (/\d+/.test(values[1])) { + offset = parseInt(values[1]); + } + + const promise = player.coordinator.getQueue(limit, offset); if (detailed) { return promise; From 768804c5404412ff4b51881b0bc0783a86bcd379 Mon Sep 17 00:00:00 2001 From: Andrew Pariser Date: Sat, 22 Jul 2017 12:44:53 -0700 Subject: [PATCH 041/123] Improve docs page (#534) --- static/index.html | 90 +++++++++++++++++++++++++++++++++-------------- 1 file changed, 63 insertions(+), 27 deletions(-) diff --git a/static/index.html b/static/index.html index e2ef1a1b..fc16b3d4 100644 --- a/static/index.html +++ b/static/index.html @@ -11,10 +11,14 @@ .logo { display: block; margin: auto } .content { margin: 20px 25%; } .docs { background-color: #f4f4f4; border-radius: 4px; padding: 10px; margin-bottom: 20px;} - .docs p { padding: 0; margin: 0; line-height: 28px; font-size: 14px; font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace; } + .docs p, .docs li { padding: 0; margin: 0; line-height: 28px; font-size: 14px; font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace; } .docs .method { background-color: #dbdbdb; padding: 4px; border-radius: 2px; margin-right: 4px;} .docs h2 { margin-bottom: 8px;} .docs h2:first-child { margin-top: 0;} + .docs h4 { margin-left: 10px; } + .docs ul { list-style-type: none; padding-left: 20px; } + .docs .explanation { margin-left: 20px; font-family: 'Helvetica Neue', Helvetica, 'Segoe UI', Arial, freesans, sans-serif; font-size: 14px; } + .docs .experimental { background-color: red; border-radius: 3px; color: white; font-size: 10px; line-height: 12px; padding: 2px 6px; font-weight: bold; } .footer { text-align: center; font-size: 12px; margin: 10px 0 50px 0;} @@ -34,8 +38,8 @@

    Info

    Global Control

    GET /lockvolumes

    GET /unlockvolumes

    -

    GET /pauseall

    -

    GET /resumeall

    +

    GET /pauseall/{timeout in minutes (optional)}

    +

    GET /resumeall/{timeout in minutes (optional)}

    GET /reindex

    GET /sleep/{timeout in seconds or timestamp HH:MM:SS or off}

    GET /preset/{JSON preset}

    @@ -47,31 +51,63 @@

    Zone Control

    GET /{zone name}/{action}[/{parameter}]

    Actions

    + +

    Playback

    +
      +
    • play
    • +
    • pause
    • +
    • playpause toggles playing state
    • +
    • trackseek/{seconds into song, i.e. 60 for 1:00, 120 for 2:00 etc.}
    • +
    • next
    • +
    • previous
    • +
    + +

    Volume

    +
      +
    • volume/{absolute volume}
    • +
    • volume/{+ or -}{relative volume}
    • +
    • groupVolume/{absolute volume}
    • +
    • groupVolume/{+ or -}{relative volume}
    • +
    • mute
    • +
    • unmute
    • +
    • groupMute
    • +
    • groupUnmute
    • +
    • togglemute
    • +
    • lockvolumes
    • +
    • unlockvolumes experimental enforce the volume that was selected when locking!
    • +
    + +

    Playback Settings

    +
      +
    • favorite
    • +
    • playlist
    • +
    • repeat/{on | off}
    • +
    • shuffle/{on | off}
    • +
    • crossfade/{on | off}
    • +
    + +

    Queue

    +
      +
    • queue
    • +
    • clearqueue
    • +
    • seek/{queue index}
    • +
    + +

    Room Grouping

    +
      +
    • add/{other zone name}
    • +
    • remove/{other zone name}
    • +
    • isolate
    • +
    + +

    Other

    +
      +
    • say
    • +
    + +

    Internals

      -
    • play
    • -
    • pause
    • -
    • playpause (toggles playing state)
    • -
    • volume (parameter is absolute or relative volume. Prefix +/- indicates relative volume)
    • -
    • groupVolume (parameter is absolute or relative volume. Prefix +/- indicates relative volume)
    • -
    • mute / unmute
    • -
    • groupMute / groupUnmute
    • -
    • togglemute (toggles mute state)
    • -
    • seek (parameter is queue index)
    • -
    • trackseek (parameter is in seconds, 60 for 1:00, 120 for 2:00 etc)
    • -
    • next
    • -
    • previous
    • -
    • state (will return a json-representation of the current state of player)
    • -
    • favorite
    • -
    • playlist
    • -
    • lockvolumes / unlockvolumes (experimental, will enforce the volume that was selected when locking!)
    • -
    • repeat (on/off)
    • -
    • shuffle (on/off)
    • -
    • crossfade (on/off)
    • -
    • pauseall (with optional timeout in minutes)
    • -
    • resumeall (will resume the ones that was pause on the pauseall call. Useful for doorbell, phone calls, etc. Optional timeout)
    • -
    • say
    • -
    • queue
    • -
    • clearqueue
    • +
    • state returns a json-representation of the current state of player
    From 59d8d43446dd63fdf4fccc9ea415d56b4a265c0f Mon Sep 17 00:00:00 2001 From: Jimmy Shimizu Date: Wed, 26 Jul 2017 21:51:25 +0200 Subject: [PATCH 042/123] Add content-length manually to microsoft request --- lib/tts-providers/microsoft.js | 3 ++- package.json | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/tts-providers/microsoft.js b/lib/tts-providers/microsoft.js index dd991e94..945d5b4b 100644 --- a/lib/tts-providers/microsoft.js +++ b/lib/tts-providers/microsoft.js @@ -83,7 +83,8 @@ function microsoft(phrase, voiceName) { 'X-Microsoft-OutputFormat': 'riff-16khz-16bit-mono-pcm', 'X-Search-AppId': APP_ID, 'X-Search-ClientID': INSTANCE_ID, - 'User-Agent': 'node-sonos-http-api' + 'User-Agent': 'node-sonos-http-api', + 'Content-Length': ssml.length }, body: ssml }) diff --git a/package.json b/package.json index d45e09a8..bd01eb60 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sonos-http-api", - "version": "1.4.1", + "version": "1.4.2", "description": "A simple node app for controlling a Sonos system with basic HTTP requests", "scripts": { "start": "node server.js" From d4f7eb97c8a0f2b289d071c8e70daddf6bb22639 Mon Sep 17 00:00:00 2001 From: Jimmy Shimizu Date: Tue, 1 Aug 2017 23:27:19 +0200 Subject: [PATCH 043/123] fix: Escape non-ascii characters as xml entities for Microsoft TTS --- lib/tts-providers/microsoft.js | 6 +++++- package.json | 3 ++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/tts-providers/microsoft.js b/lib/tts-providers/microsoft.js index 945d5b4b..1afde6fe 100644 --- a/lib/tts-providers/microsoft.js +++ b/lib/tts-providers/microsoft.js @@ -5,6 +5,9 @@ const request = require('sonos-discovery/lib/helpers/request'); const logger = require('sonos-discovery/lib/helpers/logger'); const path = require('path'); const globalSettings = require('../../settings'); +const XmlEntities = require('html-entities').XmlEntities; + +const xmlEntities = new XmlEntities(); const APP_ID = '9aa44d9e6ec14da99231a9166fd50b0f'; const INSTANCE_ID = crypto.randomBytes(16).toString('hex'); @@ -34,7 +37,8 @@ function generateBearerToken(apiKey) { } function format(lang, gender, name, text) { - return `${text}`; + const escapedText = xmlEntities.encodeNonUTF(text); + return `${escapedText}`; } function microsoft(phrase, voiceName) { diff --git a/package.json b/package.json index bd01eb60..a96dbd5d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sonos-http-api", - "version": "1.4.2", + "version": "1.4.3", "description": "A simple node app for controlling a Sonos system with basic HTTP requests", "scripts": { "start": "node server.js" @@ -15,6 +15,7 @@ "aws-sdk": "^2.12.0", "basic-auth": "~1.1.0", "fuse.js": "^2.5.0", + "html-entities": "^1.2.1", "json5": "^0.5.1", "node-static": "~0.7.0", "request-promise": "~1.0.2", From 8f2ac96a088502618b3cf0c8b767e64a4d3f7b81 Mon Sep 17 00:00:00 2001 From: Jimmy Shimizu Date: Tue, 15 Aug 2017 10:13:07 +0200 Subject: [PATCH 044/123] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 295d5651..c7803429 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -[![PayPal donate button](https://img.shields.io/badge/paypal-donate-yellow.svg)](https://www.paypal.me/jishi "Donate once-off to this project using Paypal") +[![PayPal donate button](https://img.shields.io/badge/paypal-donate-yellow.svg)](https://www.paypal.me/jishi "Donate once-off to this project using Paypal") [![Join the chat at gitter](https://img.shields.io/gitter/room/badges/shields.svg)](https://gitter.im/node-sonos-http-api/Lobby "Need assistance? Join the chat at Gitter.im") -Feel free to use it as you please. Consider donating if you want to support further development. +Feel free to use it as you please. Consider donating if you want to support further development. Reach out on the gitter chat if you have issues getting it to run, instead of creating new issues, thank you! SONOS HTTP API ============== From 1d28fd9095c37ddc370843e661ed2eef93f117a9 Mon Sep 17 00:00:00 2001 From: Jimmy Shimizu Date: Tue, 15 Aug 2017 10:14:40 +0200 Subject: [PATCH 045/123] Update ISSUE_TEMPLATE.md --- ISSUE_TEMPLATE.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md index 7e0d9acd..562fb4f4 100644 --- a/ISSUE_TEMPLATE.md +++ b/ISSUE_TEMPLATE.md @@ -1,6 +1,8 @@ ## Before you submit an issue -Search among the current open _and_ closed issues for an answer to your question before submitting an issue! +Search among the current open _and_ closed issues for an answer to your question before submitting an issue! + +If you are looking for assistance with installing or just general questions, reach out on the gitter chat (https://gitter.im/node-sonos-http-api/Lobby) instead of filing an issue. It's easier for me if issues are primarily fokused on bugs and feature requests, and hopefully other people can assist you in the chat. If your question is Docker related, please file the issue at the https://github.com/chrisns/docker-node-sonos-http-api and also, make sure you have tested that image before asking a question. This is the only image that has any correlation to this project, and is guaranteed to be up to date. From 61193265f7244c299012b6a0146dabe2532c82d5 Mon Sep 17 00:00:00 2001 From: Jimmy Shimizu Date: Sun, 27 Aug 2017 21:41:43 +0200 Subject: [PATCH 046/123] fix: Always return promise on join actions Resolves #549 --- lib/actions/group.js | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/actions/group.js b/lib/actions/group.js index 62186ea2..49fec91a 100644 --- a/lib/actions/group.js +++ b/lib/actions/group.js @@ -6,7 +6,7 @@ function addToGroup(player, values) { const joiningPlayer = player.system.getPlayer(joiningRoomName); if(!joiningPlayer) { logger.warn(`Room ${joiningRoomName} not found - can't group with ${player.roomName}`); - return; + return Promise.reject(new Error(`Room ${joiningRoomName} not found - can't group with ${player.roomName}`)); } return attachTo(joiningPlayer, player.coordinator); } @@ -16,7 +16,7 @@ function joinPlayer(player, values) { const receivingPlayer = player.system.getPlayer(receivingRoomName); if(!receivingPlayer) { logger.warn(`Room ${receivingRoomName} not found - can't make ${player.roomName} join it`); - return; + return Promise.reject(new Error(`Room ${receivingRoomName} not found - can't make ${player.roomName} join it`)); } return attachTo(player, receivingPlayer.coordinator); } diff --git a/package.json b/package.json index a96dbd5d..8bd974be 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sonos-http-api", - "version": "1.4.3", + "version": "1.4.4", "description": "A simple node app for controlling a Sonos system with basic HTTP requests", "scripts": { "start": "node server.js" From 4d07a1b632401ade2a18ae32ac73d6be1059eda8 Mon Sep 17 00:00:00 2001 From: Jimmy Shimizu Date: Sun, 10 Sep 2017 20:27:36 +0200 Subject: [PATCH 047/123] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index c7803429..ba77981b 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ Feel free to use it as you please. Consider donating if you want to support further development. Reach out on the gitter chat if you have issues getting it to run, instead of creating new issues, thank you! +If you are also looking for cloud control (ifttt, public webhooks etc), see the [bronos-client](http://www.bronos.net) project! That pi image also contain an installation of this http-api. + SONOS HTTP API ============== From 30059567b8eca0b05492a1142d976dddecc553d2 Mon Sep 17 00:00:00 2001 From: Jimmy Shimizu Date: Wed, 13 Sep 2017 23:00:24 +0200 Subject: [PATCH 048/123] fix: Make say more reliable Base fallback timer on own file parsing duration Ignore early state changes by waiting duration/2 until jacking in event listener Should fix #535 --- lib/actions/pauseall.js | 10 +- lib/actions/playpause.js | 1 - lib/actions/say.js | 4 +- lib/actions/sayall.js | 4 +- lib/helpers/all-player-announcement.js | 58 +- lib/helpers/single-player-announcement.js | 65 +- lib/helpers/try-download-tts.js | 10 +- lib/tts-providers/aws-polly.js | 27 +- lib/tts-providers/default/google.js | 44 +- lib/tts-providers/microsoft.js | 190 +++++- lib/tts-providers/voicerss.js | 27 +- package-lock.json | 729 ++++++++++++++++++++++ package.json | 7 +- 13 files changed, 1027 insertions(+), 149 deletions(-) create mode 100644 package-lock.json diff --git a/lib/actions/pauseall.js b/lib/actions/pauseall.js index b4837967..233c6226 100644 --- a/lib/actions/pauseall.js +++ b/lib/actions/pauseall.js @@ -2,11 +2,11 @@ var pausedPlayers = []; function pauseAll(player, values) { - console.log("pausing all players"); + logger.debug("pausing all players"); // save state for resume if (values[0] && values[0] > 0) { - console.log("in", values[0], "minutes"); + logger.debug("in", values[0], "minutes"); setTimeout(function () { doPauseAll(player.system); }, values[0] * 1000 * 60); @@ -17,10 +17,10 @@ function pauseAll(player, values) { } function resumeAll(player, values) { - console.log("resuming all players"); + logger.debug("resuming all players"); if (values[0] && values[0] > 0) { - console.log("in", values[0], "minutes"); + logger.debug("in", values[0], "minutes"); setTimeout(function () { doResumeAll(player.system); }, values[0] * 1000 * 60); @@ -34,11 +34,9 @@ function doPauseAll(system) { pausedPlayers = []; const promises = system.zones .filter(zone => { - console.log(zone.coordinator.state) return zone.coordinator.state.playbackState === 'PLAYING' }) .map(zone => { - console.log(zone.uuid) pausedPlayers.push(zone.uuid); const player = system.getPlayerByUUID(zone.uuid); return player.pause(); diff --git a/lib/actions/playpause.js b/lib/actions/playpause.js index ba40b71e..8732b97b 100644 --- a/lib/actions/playpause.js +++ b/lib/actions/playpause.js @@ -1,6 +1,5 @@ 'use strict'; function playpause(player) { - console.log(player.coordinator.state.playbackState) if(player.coordinator.state.playbackState === 'PLAYING') { return player.coordinator.pause(); } diff --git a/lib/actions/say.js b/lib/actions/say.js index d5e5b936..7af008a4 100644 --- a/lib/actions/say.js +++ b/lib/actions/say.js @@ -33,8 +33,8 @@ function say(player, values) { } return tryDownloadTTS(text, language) - .then((path) => { - return singlePlayerAnnouncement(player, `http://${system.localEndpoint}:${port}${path}`, announceVolume); + .then((result) => { + return singlePlayerAnnouncement(player, `http://${system.localEndpoint}:${port}${result.uri}`, announceVolume, result.duration); }); } diff --git a/lib/actions/sayall.js b/lib/actions/sayall.js index ec1a8ea4..d9fbdc8e 100644 --- a/lib/actions/sayall.js +++ b/lib/actions/sayall.js @@ -30,8 +30,8 @@ function sayAll(player, values) { return tryDownloadTTS(text, language) - .then(uri => { - return allPlayerAnnouncement(player.system, `http://${player.system.localEndpoint}:${port}${uri}`, announceVolume); + .then((result) => { + return allPlayerAnnouncement(player.system, `http://${player.system.localEndpoint}:${port}${result.uri}`, announceVolume, result.duration); }) } diff --git a/lib/helpers/all-player-announcement.js b/lib/helpers/all-player-announcement.js index 6d350ea6..d118251c 100644 --- a/lib/helpers/all-player-announcement.js +++ b/lib/helpers/all-player-announcement.js @@ -38,7 +38,7 @@ function saveAll(system) { }); } -function announceAll(system, uri, volume) { +function announceAll(system, uri, volume, duration) { let abortTimer; // Save all players @@ -72,44 +72,6 @@ function announceAll(system, uri, volume) { state: 'STOPPED' }; - let announceFinished; - let afterPlayingStateChange; - - const onTransportChange = (state) => { - // Short circuit if this announcement has been finished. - if (!announceFinished) { - return; - } - logger.debug(`playback state switched to ${state.playbackState}`); - // if (state.playbackState !== 'STOPPED') { - // player.once('transport-state', onTransportChange); - // } - - if (state.playbackState === 'STOPPED' && afterPlayingStateChange instanceof Function) { - logger.debug('announcement finished because of STOPPED state identified'); - afterPlayingStateChange(); - afterPlayingStateChange = undefined; - return; - } - - if (state.playbackState === 'PLAYING') { - afterPlayingStateChange = announceFinished; - } - - const abortDelay = coordinator._state.currentTrack.duration + 2; - clearTimeout(abortTimer); - logger.debug(`Setting restore timer for ${abortDelay} seconds`); - abortTimer = setTimeout(() => { - logger.debug(`Restoring backup preset because ${abortDelay} seconds passed`); - if (announceFinished instanceof Function) { - announceFinished(); - } - }, abortDelay * 1000); - - // This is some odd scenario where STOPPED is emitted when starting playback for some reason. - coordinator.once('transport-state', onTransportChange); - }; - const oneGroupPromise = new Promise((resolve) => { const onTopologyChanged = (topology) => { if (topology.length === 1) { @@ -122,21 +84,33 @@ function announceAll(system, uri, volume) { system.once('topology-change', onTopologyChanged); }); + const restoreTimeout = duration + 2000; return system.applyPreset(preset) .then(() => { if (system.zones.length === 1) return; return oneGroupPromise; }) .then(() => { - coordinator.once('transport-state', onTransportChange); coordinator.play(); return new Promise((resolve) => { - announceFinished = resolve; + const transportChange = (state) => { + logger.debug(`Player changed to state ${state.playbackState}`); + if (state.playbackState === 'STOPPED') { + return resolve(); + } + + coordinator.once('transport-state', onTransportChange); + }; + setTimeout(() => { + coordinator.once('transport-state', transportChange); + }, duration / 2); + + logger.debug(`Setting restore timer for ${restoreTimeout} ms`); + abortTimer = setTimeout(resolve, restoreTimeout); }); }) .then(() => { clearTimeout(abortTimer); - announceFinished = undefined; }) .then(() => { return backupPresets.reduce((promise, preset) => { diff --git a/lib/helpers/single-player-announcement.js b/lib/helpers/single-player-announcement.js index 7df96820..41627a4e 100644 --- a/lib/helpers/single-player-announcement.js +++ b/lib/helpers/single-player-announcement.js @@ -3,7 +3,7 @@ const logger = require('sonos-discovery/lib/helpers/logger'); const isRadioOrLineIn = require('../helpers/is-radio-or-line-in'); const backupPresets = {}; -function singlePlayerAnnouncement(player, uri, volume) { +function singlePlayerAnnouncement(player, uri, volume, duration) { // Create backup preset to restore this player const state = player.state; const system = player.system; @@ -57,45 +57,8 @@ function singlePlayerAnnouncement(player, uri, volume) { uri }; - let announceFinished; - let afterPlayingStateChange; let abortTimer; - const onTransportChange = (state) => { - // Short circuit if this announcement has been finished. - if (!announceFinished) { - return; - } - logger.debug(`playback state switched to ${state.playbackState}`); - // if (state.playbackState !== 'STOPPED') { - // player.once('transport-state', onTransportChange); - // } - - if (state.playbackState === 'STOPPED' && afterPlayingStateChange instanceof Function) { - logger.debug('announcement finished because of STOPPED state identified'); - afterPlayingStateChange(); - afterPlayingStateChange = undefined; - return; - } - - if (state.playbackState === 'PLAYING') { - afterPlayingStateChange = announceFinished; - } - - const abortDelay = player._state.currentTrack.duration + 2; - clearTimeout(abortTimer); - logger.debug(`Setting restore timer for ${abortDelay} seconds`); - abortTimer = setTimeout(() => { - logger.debug(`Restoring backup preset because ${abortDelay} seconds passed`); - if (announceFinished instanceof Function) { - announceFinished(); - } - }, abortDelay * 1000); - - // This is some odd scenario where STOPPED is emitted when starting playback for some reason. - player.once('transport-state', onTransportChange); - }; - if (!backupPresets[player.roomName]) { backupPresets[player.roomName] = []; } @@ -133,26 +96,38 @@ function singlePlayerAnnouncement(player, uri, volume) { }); } + let timer; + const restoreTimeout = duration + 2000; return system.applyPreset(ttsPreset) .then(() => { - // Remove any lingering event listener before attaching a new one - player.once('transport-state', onTransportChange); return new Promise((resolve) => { - announceFinished = resolve; + const transportChange = (state) => { + logger.debug(`Player changed to state ${state.playbackState}`); + if (state.playbackState === 'STOPPED') { + return resolve(); + } + + player.once('transport-state', onTransportChange); + }; + setTimeout(() => { + player.once('transport-state', transportChange); + }, duration / 2); + + logger.debug(`Setting restore timer for ${restoreTimeout} ms`); + timer = Date.now(); + abortTimer = setTimeout(resolve, restoreTimeout); }); }) .then(() => { + const elapsed = Date.now() - timer; + logger.debug(`${elapsed} elapsed with ${restoreTimeout - elapsed} to spare`); clearTimeout(abortTimer); - announceFinished = undefined; - // player.removeListener('transport-state', onTransportChange); }) .then(prepareBackupPreset) .catch((err) => { logger.error(err); - // player.removeListener('transport-state', onTransportChange); return prepareBackupPreset() .then(() => { - // we still want to inform that stuff broke throw err; }); }); diff --git a/lib/helpers/try-download-tts.js b/lib/helpers/try-download-tts.js index ad2f687f..ec05034d 100644 --- a/lib/helpers/try-download-tts.js +++ b/lib/helpers/try-download-tts.js @@ -10,14 +10,14 @@ requireDir(path.join(__dirname, '../tts-providers'), (provider) => { providers.push(require('../tts-providers/default/google')); function tryDownloadTTS(phrase, language) { - let path; + let result; return providers.reduce((promise, provider) => { return promise.then(() => { - if (path) return path; + if (result) return result; return provider(phrase, language) - .then((_path) => { - path = _path; - return path; + .then((_result) => { + result = _result; + return result; }); }); }, Promise.resolve()); diff --git a/lib/tts-providers/aws-polly.js b/lib/tts-providers/aws-polly.js index a38f094f..ab092f72 100644 --- a/lib/tts-providers/aws-polly.js +++ b/lib/tts-providers/aws-polly.js @@ -4,7 +4,9 @@ const fs = require('fs'); const http = require('http'); const path = require('path'); const AWS = require('aws-sdk'); +const musicMeta = require('music-metadata'); const settings = require('../../settings'); +const logger = require('sonos-discovery/lib/helpers/logger'); const DEFAULT_SETTINGS = { OutputFormat: 'mp3', @@ -12,6 +14,13 @@ const DEFAULT_SETTINGS = { TextType: 'text' }; +function fileDuration(path) { + return musicMeta.parseFile(path, { duration: true }) + .then((info) => { + return Math.ceil(info.format.duration*1000); + }) +} + function polly(phrase, voiceName) { if (!settings.aws) { return Promise.resolve(); @@ -35,9 +44,15 @@ function polly(phrase, voiceName) { const expectedUri = `/tts/${filename}`; try { fs.accessSync(filepath, fs.R_OK); - return Promise.resolve(expectedUri); + return fileDuration(filepath) + .then((duration) => { + return { + duration, + uri: expectedUri + }; + }); } catch (err) { - console.log(`announce file for phrase "${phrase}" does not seem to exist, downloading`); + logger.info(`announce file for phrase "${phrase}" does not seem to exist, downloading`); } const constructorParameters = Object.assign({ apiVersion: '2016-06-10' }, settings.aws.credentials); @@ -48,7 +63,13 @@ function polly(phrase, voiceName) { .promise() .then((data) => { fs.writeFileSync(filepath, data.AudioStream); - return expectedUri; + return fileDuration(filepath); + }) + .then((duration) => { + return { + duration, + uri: expectedUri + }; }); } diff --git a/lib/tts-providers/default/google.js b/lib/tts-providers/default/google.js index 61483b3e..c203ddcc 100644 --- a/lib/tts-providers/default/google.js +++ b/lib/tts-providers/default/google.js @@ -3,7 +3,16 @@ const crypto = require('crypto'); const fs = require('fs'); const http = require('http'); const path = require('path'); +const musicMeta = require('music-metadata'); const settings = require('../../../settings'); +const logger = require('sonos-discovery/lib/helpers/logger'); + +function fileDuration(path) { + return musicMeta.parseFile(path, { duration: true }) + .then((info) => { + return Math.ceil(info.format.duration * 1000); + }) +} function google(phrase, language) { if (!language) { @@ -12,23 +21,33 @@ function google(phrase, language) { // Use Google tts translation service to create a mp3 file const ttsRequestUrl = 'http://translate.google.com/translate_tts?client=tw-ob&tl=' + language + '&q=' + encodeURIComponent(phrase); - + // Construct a filesystem neutral filename const phraseHash = crypto.createHash('sha1').update(phrase).digest('hex'); const filename = `google-${phraseHash}-${language}.mp3`; const filepath = path.resolve(settings.webroot, 'tts', filename); - + const expectedUri = `/tts/${filename}`; try { fs.accessSync(filepath, fs.R_OK); - return Promise.resolve(expectedUri); + return fileDuration(filepath) + .then((duration) => { + return { + duration, + uri: expectedUri + }; + }); } catch (err) { - console.log(`announce file for phrase "${phrase}" does not seem to exist, downloading`); + logger.info(`announce file for phrase "${phrase}" does not seem to exist, downloading`); } return new Promise((resolve, reject) => { var file = fs.createWriteStream(filepath); - var options = {"headers": {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36"}, "host": "translate.google.com", "path": "/translate_tts?client=tw-ob&tl=" + language + "&q=" + encodeURIComponent(phrase) } + var options = { + "headers": { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36" }, + "host": "translate.google.com", + "path": "/translate_tts?client=tw-ob&tl=" + language + "&q=" + encodeURIComponent(phrase) + } var callback = function (response) { if (response.statusCode < 300 && response.statusCode >= 200) { response.pipe(file); @@ -38,15 +57,24 @@ function google(phrase, language) { }); } else { reject(new Error(`Download from google TTS failed with status ${response.statusCode}, ${response.message}`)); - + } } - + http.request(options, callback).on('error', function (err) { fs.unlink(dest); reject(err); }).end(); - }); + }) + .then(() => { + return fileDuration(filepath); + }) + .then((duration) => { + return { + duration, + uri: expectedUri + }; + }); } module.exports = google; \ No newline at end of file diff --git a/lib/tts-providers/microsoft.js b/lib/tts-providers/microsoft.js index 1afde6fe..609a5618 100644 --- a/lib/tts-providers/microsoft.js +++ b/lib/tts-providers/microsoft.js @@ -1,9 +1,10 @@ 'use strict'; const crypto = require('crypto'); const fs = require('fs'); +const path = require('path'); +const musicMeta = require('music-metadata'); const request = require('sonos-discovery/lib/helpers/request'); const logger = require('sonos-discovery/lib/helpers/logger'); -const path = require('path'); const globalSettings = require('../../settings'); const XmlEntities = require('html-entities').XmlEntities; @@ -41,6 +42,13 @@ function format(lang, gender, name, text) { return `${escapedText}`; } +function fileDuration(path) { + return musicMeta.parseFile(path, { duration: true }) + .then((info) => { + return Math.ceil(info.format.duration*1000); + }) +} + function microsoft(phrase, voiceName) { if (!globalSettings.microsoft || !globalSettings.microsoft.key) { return Promise.resolve(); @@ -53,13 +61,20 @@ function microsoft(phrase, voiceName) { } const phraseHash = crypto.createHash('sha1').update(phrase).digest('hex'); - const filename = `microsoft-${phraseHash}-${settings.name}.wav`; + const filename = `microsoft-${phraseHash}-${settings.name}.wav`; const filepath = path.resolve(globalSettings.webroot, 'tts', filename); const expectedUri = `/tts/${filename}`; try { fs.accessSync(filepath, fs.R_OK); - return Promise.resolve(expectedUri); + return fileDuration(filepath) + .then((duration) => { + return { + duration, + uri: expectedUri + }; + }); + } catch (err) { logger.info(`announce file for phrase "${phrase}" does not seem to exist, downloading`); } @@ -75,7 +90,7 @@ function microsoft(phrase, voiceName) { if (!voice) { throw new Error(`Voice name ${settings.name} could not be located in the list of valid voice names`); } - + const ssml = format(voice.language, voice.gender, voice.font, phrase); return request({ uri: 'https://speech.platform.bing.com/synthesize', @@ -99,11 +114,20 @@ function microsoft(phrase, voiceName) { res.pipe(file); res.on('end', () => { - resolve(expectedUri); + resolve(); }) }) }) + .then(() => { + return fileDuration(filepath); + }) + .then((duration) => { + return { + duration, + uri: expectedUri + }; + }) .catch((err) => { logger.error(err); throw err; @@ -114,33 +138,137 @@ function microsoft(phrase, voiceName) { const VOICE = { Hoda: { language: 'ar-EG', gender: 'Female', font: 'Microsoft Server Speech Text to Speech Voice (ar-EG, Hoda)' }, Hedda: { language: 'de-DE', gender: 'Female', font: 'Microsoft Server Speech Text to Speech Voice (de-DE, Hedda)' }, - Stefan: { language: 'de-DE', gender: 'Male', font: 'Microsoft Server Speech Text to Speech Voice (de-DE, Stefan, Apollo)' }, - Catherine: { language: 'en-AU', gender: 'Female', font: 'Microsoft Server Speech Text to Speech Voice (en-AU, Catherine)' }, + Stefan: { + language: 'de-DE', + gender: 'Male', + font: 'Microsoft Server Speech Text to Speech Voice (de-DE, Stefan, Apollo)' + }, + Catherine: { + language: 'en-AU', + gender: 'Female', + font: 'Microsoft Server Speech Text to Speech Voice (en-AU, Catherine)' + }, Linda: { language: 'en-CA', gender: 'Female', font: 'Microsoft Server Speech Text to Speech Voice (en-CA, Linda)' }, - Susan: { language: 'en-GB', gender: 'Female', font: 'Microsoft Server Speech Text to Speech Voice (en-GB, Susan, Apollo)' }, - George: { language: 'en-GB', gender: 'Male', font: 'Microsoft Server Speech Text to Speech Voice (en-GB, George, Apollo)' }, - Ravi: { language: 'en-IN', gender: 'Male', font: 'Microsoft Server Speech Text to Speech Voice (en-IN, Ravi, Apollo)' }, - ZiraRUS: { language: 'en-US', gender: 'Female', font: 'Microsoft Server Speech Text to Speech Voice (en-US, ZiraRUS)' }, - BenjaminRUS: { language: 'en-US', gender: 'Male', font: 'Microsoft Server Speech Text to Speech Voice (en-US, BenjaminRUS)' }, - Laura: { language: 'es-ES', gender: 'Female', font: 'Microsoft Server Speech Text to Speech Voice (es-ES, Laura, Apollo)' }, - Pablo: { language: 'es-ES', gender: 'Male', font: 'Microsoft Server Speech Text to Speech Voice (es-ES, Pablo, Apollo)' }, - Raul: { language: 'es-MX', gender: 'Male', font: 'Microsoft Server Speech Text to Speech Voice (es-MX, Raul, Apollo)' }, - Caroline: { language: 'fr-CA', gender: 'Female', font: 'Microsoft Server Speech Text to Speech Voice (fr-CA, Caroline)' }, - Julie: { language: 'fr-FR', gender: 'Female', font: 'Microsoft Server Speech Text to Speech Voice (fr-FR, Julie, Apollo)' }, - Paul: { language: 'fr-FR', gender: 'Male', font: 'Microsoft Server Speech Text to Speech Voice (fr-FR, Paul, Apollo)' }, - Cosimo: { language: 'it-IT', gender: 'Male', font: 'Microsoft Server Speech Text to Speech Voice (it-IT, Cosimo, Apollo)' }, - Ayumi: { language: 'ja-JP', gender: 'Female', font: 'Microsoft Server Speech Text to Speech Voice (ja-JP, Ayumi, Apollo)' }, - Ichiro: { language: 'ja-JP', gender: 'Male', font: 'Microsoft Server Speech Text to Speech Voice (ja-JP, Ichiro, Apollo)' }, - Daniel: { language: 'pt-BR', gender: 'Male', font: 'Microsoft Server Speech Text to Speech Voice (pt-BR, Daniel, Apollo)' }, - Irina: { language: 'ru-RU', gender: 'Female', font: 'Microsoft Server Speech Text to Speech Voice (ru-RU, Irina, Apollo)' }, - Pavel: { language: 'ru-RU', gender: 'Male', font: 'Microsoft Server Speech Text to Speech Voice (ru-RU, Pavel, Apollo)' }, - HuihuiRUS: { language: 'zh-CN', gender: 'Female', font: 'Microsoft Server Speech Text to Speech Voice (zh-CN, HuihuiRUS)' }, - Yaoyao: { language: 'zh-CN', gender: 'Female', font: 'Microsoft Server Speech Text to Speech Voice (zh-CN, Yaoyao, Apollo)' }, - Kangkang: { language: 'zh-CN', gender: 'Male', font: 'Microsoft Server Speech Text to Speech Voice (zh-CN, Kangkang, Apollo)' }, - Tracy: { language: 'zh-HK', gender: 'Female', font: 'Microsoft Server Speech Text to Speech Voice (zh-HK, Tracy, Apollo)' }, - Danny: { language: 'zh-HK', gender: 'Male', font: 'Microsoft Server Speech Text to Speech Voice (zh-HK, Danny, Apollo)' }, - Yating: { language: 'zh-TW', gender: 'Female', font: 'Microsoft Server Speech Text to Speech Voice (zh-TW, Yating, Apollo)' }, - Zhiwei: { language: 'zh-TW', gender: 'Male', font: 'Microsoft Server Speech Text to Speech Voice (zh-TW, Zhiwei, Apollo)' } + Susan: { + language: 'en-GB', + gender: 'Female', + font: 'Microsoft Server Speech Text to Speech Voice (en-GB, Susan, Apollo)' + }, + George: { + language: 'en-GB', + gender: 'Male', + font: 'Microsoft Server Speech Text to Speech Voice (en-GB, George, Apollo)' + }, + Ravi: { + language: 'en-IN', + gender: 'Male', + font: 'Microsoft Server Speech Text to Speech Voice (en-IN, Ravi, Apollo)' + }, + ZiraRUS: { + language: 'en-US', + gender: 'Female', + font: 'Microsoft Server Speech Text to Speech Voice (en-US, ZiraRUS)' + }, + BenjaminRUS: { + language: 'en-US', + gender: 'Male', + font: 'Microsoft Server Speech Text to Speech Voice (en-US, BenjaminRUS)' + }, + Laura: { + language: 'es-ES', + gender: 'Female', + font: 'Microsoft Server Speech Text to Speech Voice (es-ES, Laura, Apollo)' + }, + Pablo: { + language: 'es-ES', + gender: 'Male', + font: 'Microsoft Server Speech Text to Speech Voice (es-ES, Pablo, Apollo)' + }, + Raul: { + language: 'es-MX', + gender: 'Male', + font: 'Microsoft Server Speech Text to Speech Voice (es-MX, Raul, Apollo)' + }, + Caroline: { + language: 'fr-CA', + gender: 'Female', + font: 'Microsoft Server Speech Text to Speech Voice (fr-CA, Caroline)' + }, + Julie: { + language: 'fr-FR', + gender: 'Female', + font: 'Microsoft Server Speech Text to Speech Voice (fr-FR, Julie, Apollo)' + }, + Paul: { + language: 'fr-FR', + gender: 'Male', + font: 'Microsoft Server Speech Text to Speech Voice (fr-FR, Paul, Apollo)' + }, + Cosimo: { + language: 'it-IT', + gender: 'Male', + font: 'Microsoft Server Speech Text to Speech Voice (it-IT, Cosimo, Apollo)' + }, + Ayumi: { + language: 'ja-JP', + gender: 'Female', + font: 'Microsoft Server Speech Text to Speech Voice (ja-JP, Ayumi, Apollo)' + }, + Ichiro: { + language: 'ja-JP', + gender: 'Male', + font: 'Microsoft Server Speech Text to Speech Voice (ja-JP, Ichiro, Apollo)' + }, + Daniel: { + language: 'pt-BR', + gender: 'Male', + font: 'Microsoft Server Speech Text to Speech Voice (pt-BR, Daniel, Apollo)' + }, + Irina: { + language: 'ru-RU', + gender: 'Female', + font: 'Microsoft Server Speech Text to Speech Voice (ru-RU, Irina, Apollo)' + }, + Pavel: { + language: 'ru-RU', + gender: 'Male', + font: 'Microsoft Server Speech Text to Speech Voice (ru-RU, Pavel, Apollo)' + }, + HuihuiRUS: { + language: 'zh-CN', + gender: 'Female', + font: 'Microsoft Server Speech Text to Speech Voice (zh-CN, HuihuiRUS)' + }, + Yaoyao: { + language: 'zh-CN', + gender: 'Female', + font: 'Microsoft Server Speech Text to Speech Voice (zh-CN, Yaoyao, Apollo)' + }, + Kangkang: { + language: 'zh-CN', + gender: 'Male', + font: 'Microsoft Server Speech Text to Speech Voice (zh-CN, Kangkang, Apollo)' + }, + Tracy: { + language: 'zh-HK', + gender: 'Female', + font: 'Microsoft Server Speech Text to Speech Voice (zh-HK, Tracy, Apollo)' + }, + Danny: { + language: 'zh-HK', + gender: 'Male', + font: 'Microsoft Server Speech Text to Speech Voice (zh-HK, Danny, Apollo)' + }, + Yating: { + language: 'zh-TW', + gender: 'Female', + font: 'Microsoft Server Speech Text to Speech Voice (zh-TW, Yating, Apollo)' + }, + Zhiwei: { + language: 'zh-TW', + gender: 'Male', + font: 'Microsoft Server Speech Text to Speech Voice (zh-TW, Zhiwei, Apollo)' + } }; module.exports = microsoft; diff --git a/lib/tts-providers/voicerss.js b/lib/tts-providers/voicerss.js index 57df31d5..216cf9da 100644 --- a/lib/tts-providers/voicerss.js +++ b/lib/tts-providers/voicerss.js @@ -3,8 +3,16 @@ const crypto = require('crypto'); const fs = require('fs'); const http = require('http'); const path = require('path'); +const musicMeta = require('music-metadata'); const settings = require('../../settings'); +function fileDuration(path) { + return musicMeta.parseFile(path, { duration: true }) + .then((info) => { + return Math.ceil(info.format.duration*1000); + }) +} + function voicerss(phrase, language) { if (!settings.voicerss) { return Promise.resolve(); @@ -25,7 +33,13 @@ function voicerss(phrase, language) { const expectedUri = `/tts/${filename}`; try { fs.accessSync(filepath, fs.R_OK); - return Promise.resolve(expectedUri); + return fileDuration(filepath) + .then((duration) => { + return { + duration, + uri: expectedUri + }; + }); } catch (err) { console.log(`announce file for phrase "${phrase}" does not seem to exist, downloading`); } @@ -47,7 +61,16 @@ function voicerss(phrase, language) { fs.unlink(dest); reject(err); }); - }); + }) + .then(() => { + return fileDuration(filepath); + }) + .then((duration) => { + return { + duration, + uri: expectedUri + }; + }); } module.exports = voicerss; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..e2cee43a --- /dev/null +++ b/package-lock.json @@ -0,0 +1,729 @@ +{ + "name": "sonos-http-api", + "version": "1.4.4", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@types/es6-promise": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/@types/es6-promise/-/es6-promise-0.0.33.tgz", + "integrity": "sha512-HKJFVLCGrWQ/1unEw8JdaTxu6n3EUxmwTxJ6D0O1x0gD8joCsgoTWxEgevb7fp2XIogNjof3KEd+3bJoGne/nw==" + }, + "ajv": { + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", + "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=", + "requires": { + "co": "4.6.0", + "json-stable-stringify": "1.0.1" + } + }, + "anesidora": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/anesidora/-/anesidora-1.2.1.tgz", + "integrity": "sha1-/ZWdrLiPx6im5xE+OA8/rQmTipo=", + "requires": { + "request": "2.81.0", + "underscore": "1.8.3" + } + }, + "asn1": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", + "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=" + }, + "assert-plus": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz", + "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=" + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "aws-sdk": { + "version": "2.114.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.114.0.tgz", + "integrity": "sha1-NckZ6N4c/ZbtplzYpmGZcyR7tzc=", + "requires": { + "buffer": "4.9.1", + "crypto-browserify": "1.0.9", + "events": "1.1.1", + "jmespath": "0.15.0", + "querystring": "0.2.0", + "sax": "1.2.1", + "url": "0.10.3", + "uuid": "3.0.1", + "xml2js": "0.4.17", + "xmlbuilder": "4.2.1" + }, + "dependencies": { + "uuid": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.0.1.tgz", + "integrity": "sha1-ZUS7ot/ajBzxfmKaOjBeK7H+5sE=" + } + } + }, + "aws-sign2": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz", + "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=" + }, + "aws4": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz", + "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=" + }, + "base64-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.2.1.tgz", + "integrity": "sha512-dwVUVIXsBZXwTuwnXI9RK8sBmgq09NDHzyR9SAph9eqk76gKK2JSQmZARC2zRC81JC2QTtxD0ARU5qTS25gIGw==" + }, + "basic-auth": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-1.1.0.tgz", + "integrity": "sha1-RSIe5Cn37h5QNb4/UVM/HN/SmIQ=" + }, + "bcrypt-pbkdf": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", + "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", + "optional": true, + "requires": { + "tweetnacl": "0.14.5" + } + }, + "bluebird": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-2.11.0.tgz", + "integrity": "sha1-U0uQM8AiyVecVro7Plpcqvu2UOE=" + }, + "boom": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", + "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=", + "requires": { + "hoek": "2.16.3" + } + }, + "buffer": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", + "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", + "requires": { + "base64-js": "1.2.1", + "ieee754": "1.1.8", + "isarray": "1.0.0" + } + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + }, + "cls-bluebird": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/cls-bluebird/-/cls-bluebird-1.1.3.tgz", + "integrity": "sha1-syY8EaCJsDlhhaG3q5BNkPAq1Cg=", + "requires": { + "is-bluebird": "1.0.2", + "shimmer": "1.1.0" + } + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" + }, + "colors": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz", + "integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=" + }, + "combined-stream": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", + "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=", + "requires": { + "delayed-stream": "1.0.0" + } + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "cryptiles": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz", + "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=", + "requires": { + "boom": "2.10.1" + } + }, + "crypto-browserify": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-1.0.9.tgz", + "integrity": "sha1-zFRJaF37hesRyYKKzHy4erW7/MA=" + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "requires": { + "assert-plus": "1.0.0" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + } + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, + "ecc-jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", + "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", + "optional": true, + "requires": { + "jsbn": "0.1.1" + } + }, + "es6-promise": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.1.1.tgz", + "integrity": "sha512-OaU1hHjgJf+b0NzsxCg7NdIYERD6Hy/PEmFLTjw+b65scuisG3Kt4QoTvJ66BBkPZ581gr0kpoVzKnxniM8nng==" + }, + "events": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", + "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=" + }, + "extend": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", + "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=" + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + }, + "form-data": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz", + "integrity": "sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE=", + "requires": { + "asynckit": "0.4.0", + "combined-stream": "1.0.5", + "mime-types": "2.1.17" + } + }, + "fs-extra": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.2.tgz", + "integrity": "sha1-+RcExT0bRh+JNFKwwwfZmXZHq2s=", + "requires": { + "graceful-fs": "4.1.11", + "jsonfile": "4.0.0", + "universalify": "0.1.1" + } + }, + "fuse.js": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-2.7.4.tgz", + "integrity": "sha1-luQg/efvARrEnCWKYhMU/ldlNvk=" + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "requires": { + "assert-plus": "1.0.0" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + } + } + }, + "graceful-fs": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=" + }, + "har-schema": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-1.0.5.tgz", + "integrity": "sha1-0mMTX0MwfALGAq/I/pWXDAFRNp4=" + }, + "har-validator": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-4.2.1.tgz", + "integrity": "sha1-M0gdDxu/9gDdID11gSpqX7oALio=", + "requires": { + "ajv": "4.11.8", + "har-schema": "1.0.5" + } + }, + "hawk": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", + "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=", + "requires": { + "boom": "2.10.1", + "cryptiles": "2.0.5", + "hoek": "2.16.3", + "sntp": "1.0.9" + } + }, + "hoek": { + "version": "2.16.3", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", + "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=" + }, + "html-entities": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.2.1.tgz", + "integrity": "sha1-DfKTUfByEWNRXfueVUPl9u7VFi8=" + }, + "http-signature": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz", + "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=", + "requires": { + "assert-plus": "0.2.0", + "jsprim": "1.4.1", + "sshpk": "1.13.1" + } + }, + "ieee754": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.8.tgz", + "integrity": "sha1-vjPUCsEO8ZJnAfbwii2G+/0a0+Q=" + }, + "is-bluebird": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-bluebird/-/is-bluebird-1.0.2.tgz", + "integrity": "sha1-CWQ5Bg9KpBGr7hkUOoTWpVNG1uI=" + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + }, + "jmespath": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz", + "integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc=" + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "optional": true + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + }, + "json-stable-stringify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", + "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", + "requires": { + "jsonify": "0.0.0" + } + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + }, + "json5": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", + "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=" + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "requires": { + "graceful-fs": "4.1.11" + } + }, + "jsonify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", + "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=" + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + } + } + }, + "lodash": { + "version": "4.17.4", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", + "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=" + }, + "mime": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.0.tgz", + "integrity": "sha512-n9ChLv77+QQEapYz8lV+rIZAW3HhAPW2CXnzb1GN5uMkuczshwvkW7XPsbzU0ZQN3sP47Er2KVkp2p3KyqZKSQ==" + }, + "mime-db": { + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.30.0.tgz", + "integrity": "sha1-dMZD2i3Z1qRTmZY0ZbJtXKfXHwE=" + }, + "mime-types": { + "version": "2.1.17", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz", + "integrity": "sha1-Cdejk/A+mVp5+K+Fe3Cp4KsWVXo=", + "requires": { + "mime-db": "1.30.0" + } + }, + "minimist": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", + "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=" + }, + "music-metadata": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/music-metadata/-/music-metadata-0.8.4.tgz", + "integrity": "sha512-vPa0VUnRMgVlui/yoFN+v7pn5mTdgfZSNLS1+AsYLcVxHGMMO/hCly9ICw/vRJTyQuVApEOdiiXr8pxCIa3HuA==", + "requires": { + "es6-promise": "4.1.1", + "fs-extra": "4.0.2", + "strtok3": "1.3.2", + "then-read-stream": "1.0.3", + "token-types": "0.9.1" + } + }, + "node-static": { + "version": "0.7.9", + "resolved": "https://registry.npmjs.org/node-static/-/node-static-0.7.9.tgz", + "integrity": "sha1-m7afziKB96480fuYPp6g7AzZ/ss=", + "requires": { + "colors": "1.1.2", + "mime": "1.4.0", + "optimist": "0.6.1" + } + }, + "oauth-sign": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", + "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=" + }, + "optimist": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", + "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", + "requires": { + "minimist": "0.0.10", + "wordwrap": "0.0.3" + } + }, + "performance-now": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-0.2.0.tgz", + "integrity": "sha1-M+8wxcd9TqIcWlOGnZG1bY8lVeU=" + }, + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + }, + "qs": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz", + "integrity": "sha1-E+JtKK1rD/qpExLNO/cI7TUecjM=" + }, + "querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" + }, + "request": { + "version": "2.81.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.81.0.tgz", + "integrity": "sha1-xpKJRqDgbF+Nb4qTM0af/aRimKA=", + "requires": { + "aws-sign2": "0.6.0", + "aws4": "1.6.0", + "caseless": "0.12.0", + "combined-stream": "1.0.5", + "extend": "3.0.1", + "forever-agent": "0.6.1", + "form-data": "2.1.4", + "har-validator": "4.2.1", + "hawk": "3.1.3", + "http-signature": "1.1.1", + "is-typedarray": "1.0.0", + "isstream": "0.1.2", + "json-stringify-safe": "5.0.1", + "mime-types": "2.1.17", + "oauth-sign": "0.8.2", + "performance-now": "0.2.0", + "qs": "6.4.0", + "safe-buffer": "5.1.1", + "stringstream": "0.0.5", + "tough-cookie": "2.3.2", + "tunnel-agent": "0.6.0", + "uuid": "3.1.0" + } + }, + "request-promise": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/request-promise/-/request-promise-1.0.2.tgz", + "integrity": "sha1-FV9BBgjZJXwInB0LJvjY96iqhqE=", + "requires": { + "bluebird": "2.11.0", + "cls-bluebird": "1.1.3", + "lodash": "3.10.1", + "request": "2.81.0" + }, + "dependencies": { + "lodash": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", + "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=" + } + } + }, + "safe-buffer": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", + "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" + }, + "sax": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", + "integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o=" + }, + "shimmer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.1.0.tgz", + "integrity": "sha1-l9c3cTf/u6tCVSLkKf4KqJpIizU=" + }, + "sntp": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz", + "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=", + "requires": { + "hoek": "2.16.3" + } + }, + "sonos-discovery": { + "version": "https://github.com/jishi/node-sonos-discovery/archive/v1.5.1.tar.gz", + "integrity": "sha1-spQ5J3EcY3L1pYyuCzf5p833NLA=", + "requires": { + "html-entities": "1.0.10", + "xml-flow": "1.0.1" + }, + "dependencies": { + "html-entities": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.0.10.tgz", + "integrity": "sha1-DepZEw3VDfFU6CxM9x0Iwsnclos=" + } + } + }, + "sshpk": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.1.tgz", + "integrity": "sha1-US322mKHFEMW3EwY/hzx2UBzm+M=", + "requires": { + "asn1": "0.2.3", + "assert-plus": "1.0.0", + "bcrypt-pbkdf": "1.0.1", + "dashdash": "1.14.1", + "ecc-jsbn": "0.1.1", + "getpass": "0.1.7", + "jsbn": "0.1.1", + "tweetnacl": "0.14.5" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + } + } + }, + "stringstream": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", + "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=" + }, + "strtok3": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-1.3.2.tgz", + "integrity": "sha1-w6jpgrb4+NQVCilvzhUXzcTsJ9E=", + "requires": { + "@types/es6-promise": "0.0.33", + "es6-promise": "4.1.1", + "fs-extra": "4.0.2", + "then-read-stream": "1.0.3", + "token-types": "0.9.1" + } + }, + "then-read-stream": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/then-read-stream/-/then-read-stream-1.0.3.tgz", + "integrity": "sha1-lwR4ZiqR382+APnxNfF7zPKQcf8=" + }, + "token-types": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/token-types/-/token-types-0.9.1.tgz", + "integrity": "sha1-xfm+Wfs7HsfLeAtAglMvkuVp+8Y=" + }, + "tough-cookie": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.2.tgz", + "integrity": "sha1-8IH3bkyFcg5sN6X6ztc3FQ2EByo=", + "requires": { + "punycode": "1.4.1" + } + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "requires": { + "safe-buffer": "5.1.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "optional": true + }, + "underscore": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz", + "integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI=" + }, + "universalify": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.1.tgz", + "integrity": "sha1-+nG63UQ3r0wUiEHjs7Fl+enlkLc=" + }, + "url": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", + "integrity": "sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=", + "requires": { + "punycode": "1.3.2", + "querystring": "0.2.0" + }, + "dependencies": { + "punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" + } + } + }, + "uuid": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz", + "integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g==" + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "requires": { + "assert-plus": "1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "1.3.0" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + } + } + }, + "wav-file-info": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/wav-file-info/-/wav-file-info-0.0.8.tgz", + "integrity": "sha1-aAp160w0a34/RX55AqexmDUG5N4=" + }, + "wordwrap": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", + "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=" + }, + "xml-flow": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xml-flow/-/xml-flow-1.0.1.tgz", + "integrity": "sha1-jHfVkY8wciQ8MJV6RsJnlFP+cLs=", + "requires": { + "sax": "1.2.1" + } + }, + "xml2js": { + "version": "0.4.17", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.17.tgz", + "integrity": "sha1-F76T6q4/O3eTWceVtBlwWogX6Gg=", + "requires": { + "sax": "1.2.1", + "xmlbuilder": "4.2.1" + } + }, + "xmlbuilder": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-4.2.1.tgz", + "integrity": "sha1-qlijBBoGb5DqoWwvU4n/GfP0YaU=", + "requires": { + "lodash": "4.17.4" + } + } + } +} diff --git a/package.json b/package.json index 8bd974be..48f9d586 100644 --- a/package.json +++ b/package.json @@ -17,9 +17,12 @@ "fuse.js": "^2.5.0", "html-entities": "^1.2.1", "json5": "^0.5.1", - "node-static": "~0.7.0", + "mime": "^1.4.0", + "music-metadata": "^0.8.4", + "node-static": "^0.7.9", "request-promise": "~1.0.2", - "sonos-discovery": "https://github.com/jishi/node-sonos-discovery/archive/v1.5.1.tar.gz" + "sonos-discovery": "https://github.com/jishi/node-sonos-discovery/archive/v1.5.1.tar.gz", + "wav-file-info": "0.0.8" }, "engines": { "node": ">=4.0.0", From 91f39207120e608cac6e36d2065d96d1429a383f Mon Sep 17 00:00:00 2001 From: Jimmy Shimizu Date: Sun, 10 Sep 2017 20:27:36 +0200 Subject: [PATCH 049/123] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index c7803429..ba77981b 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ Feel free to use it as you please. Consider donating if you want to support further development. Reach out on the gitter chat if you have issues getting it to run, instead of creating new issues, thank you! +If you are also looking for cloud control (ifttt, public webhooks etc), see the [bronos-client](http://www.bronos.net) project! That pi image also contain an installation of this http-api. + SONOS HTTP API ============== From b0efea0e95b8e77854365dee982597a959e38fd7 Mon Sep 17 00:00:00 2001 From: Jimmy Shimizu Date: Wed, 13 Sep 2017 23:05:35 +0200 Subject: [PATCH 050/123] chore: Up version to 1.4.5 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 48f9d586..a96bb235 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sonos-http-api", - "version": "1.4.4", + "version": "1.4.5", "description": "A simple node app for controlling a Sonos system with basic HTTP requests", "scripts": { "start": "node server.js" From 05a0f0d4d000609ad8dae8eb0f69ef4fec0b3a76 Mon Sep 17 00:00:00 2001 From: Jimmy Shimizu Date: Sun, 1 Oct 2017 18:45:27 +0200 Subject: [PATCH 051/123] fix: clip/clipall now calculates duration properly --- .eslintrc | 11 + .gitignore | 2 + lib/actions/clip.js | 13 +- lib/actions/clipall.js | 9 +- lib/helpers/all-player-announcement.js | 4 +- lib/helpers/file-duration.js | 10 + lib/helpers/single-player-announcement.js | 6 +- lib/tts-providers/aws-polly.js | 15 +- lib/tts-providers/default/google.js | 18 +- lib/tts-providers/microsoft.js | 9 +- lib/tts-providers/voicerss.js | 21 +- package-lock.json | 1453 ++++++++++++++++++++- package.json | 12 +- 13 files changed, 1522 insertions(+), 61 deletions(-) create mode 100644 .eslintrc create mode 100644 lib/helpers/file-duration.js diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 00000000..5aab52d6 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,11 @@ +{ + "extends": "airbnb-base", + "env": { + "node": true, + "mocha": true + }, + "rules": { + "comma-dangle": 0, + "strict": 0 + } +} diff --git a/.gitignore b/.gitignore index d2c2d964..be7ee133 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,5 @@ obj/ # Ignore Mac DS_Store files .DS_Store +yarn.lock +npm-debug.log diff --git a/lib/actions/clip.js b/lib/actions/clip.js index a566b0a0..479d8285 100644 --- a/lib/actions/clip.js +++ b/lib/actions/clip.js @@ -1,13 +1,13 @@ 'use strict'; const path = require('path'); -const crypto = require('crypto'); -const fs = require('fs'); -const http = require('http'); +const fileDuration = require('../helpers/file-duration'); const settings = require('../../settings'); const singlePlayerAnnouncement = require('../helpers/single-player-announcement'); let port; +const LOCAL_PATH_LOCATION = path.join(settings.webroot, 'clips'); + const backupPresets = {}; function playClip(player, values) { @@ -19,10 +19,13 @@ function playClip(player, values) { announceVolume = values[1]; } - return singlePlayerAnnouncement(player, `http://${player.system.localEndpoint}:${port}/clips/${clipFileName}`, announceVolume); + return fileDuration(path.join(LOCAL_PATH_LOCATION, clipFileName)) + .then((duration) => { + return singlePlayerAnnouncement(player, `http://${player.system.localEndpoint}:${port}/clips/${clipFileName}`, announceVolume, duration); + }); } -module.exports = function (api) { +module.exports = function(api) { port = api.getPort(); api.registerAction('clip', playClip); } diff --git a/lib/actions/clipall.js b/lib/actions/clipall.js index 4395a019..fe969356 100644 --- a/lib/actions/clipall.js +++ b/lib/actions/clipall.js @@ -1,9 +1,13 @@ 'use strict'; +const path = require('path'); const settings = require('../../settings'); const allPlayerAnnouncement = require('../helpers/all-player-announcement'); +const fileDuration = require('../helpers/file-duration'); let port; +const LOCAL_PATH_LOCATION = path.join(settings.webroot, 'clips'); + function playClipOnAll(player, values) { const clipFileName = values[0]; let announceVolume = settings.announceVolume || 40; @@ -13,7 +17,10 @@ function playClipOnAll(player, values) { announceVolume = values[1]; } - return allPlayerAnnouncement(player.system, `http://${player.system.localEndpoint}:${port}/clips/${clipFileName}`, announceVolume); + return fileDuration(path.join(LOCAL_PATH_LOCATION, clipFileName)) + .then((duration) => { + return allPlayerAnnouncement(player.system, `http://${player.system.localEndpoint}:${port}/clips/${clipFileName}`, announceVolume, duration); + }); } module.exports = function (api) { diff --git a/lib/helpers/all-player-announcement.js b/lib/helpers/all-player-announcement.js index d118251c..a55a1ce2 100644 --- a/lib/helpers/all-player-announcement.js +++ b/lib/helpers/all-player-announcement.js @@ -99,7 +99,7 @@ function announceAll(system, uri, volume, duration) { return resolve(); } - coordinator.once('transport-state', onTransportChange); + coordinator.once('transport-state', transportChange); }; setTimeout(() => { coordinator.once('transport-state', transportChange); @@ -125,4 +125,4 @@ function announceAll(system, uri, volume, duration) { } -module.exports = announceAll; \ No newline at end of file +module.exports = announceAll; diff --git a/lib/helpers/file-duration.js b/lib/helpers/file-duration.js new file mode 100644 index 00000000..1055acbf --- /dev/null +++ b/lib/helpers/file-duration.js @@ -0,0 +1,10 @@ +const musicMeta = require('music-metadata'); + +function fileDuration(path) { + return musicMeta.parseFile(path, { duration: true }) + .then((info) => { + return Math.ceil(info.format.duration * 1000); + }) +} + +module.exports = fileDuration; diff --git a/lib/helpers/single-player-announcement.js b/lib/helpers/single-player-announcement.js index 41627a4e..b84f6e51 100644 --- a/lib/helpers/single-player-announcement.js +++ b/lib/helpers/single-player-announcement.js @@ -21,7 +21,7 @@ function singlePlayerAnnouncement(player, uri, volume, duration) { // remember which group you were part of. const group = system.zones.find(zone => zone.coordinator.uuid === player.coordinator.uuid); if (group.members.length > 1) { - console.log('Think its coordinator, will find uri later'); + logger.debug('Think its coordinator, will find uri later'); groupToRejoin = group.id; backupPreset.group = group.id; } else { @@ -92,7 +92,7 @@ function singlePlayerAnnouncement(player, uri, volume, duration) { return system.applyPreset(relevantBackupPreset) .then(() => { backupPresets[player.roomName].shift(); - console.log('after backup preset applied', backupPresets[player.roomName]); + logger.debug('after backup preset applied', backupPresets[player.roomName]); }); } @@ -133,4 +133,4 @@ function singlePlayerAnnouncement(player, uri, volume, duration) { }); } -module.exports = singlePlayerAnnouncement; \ No newline at end of file +module.exports = singlePlayerAnnouncement; diff --git a/lib/tts-providers/aws-polly.js b/lib/tts-providers/aws-polly.js index ab092f72..bd7aa35e 100644 --- a/lib/tts-providers/aws-polly.js +++ b/lib/tts-providers/aws-polly.js @@ -4,7 +4,7 @@ const fs = require('fs'); const http = require('http'); const path = require('path'); const AWS = require('aws-sdk'); -const musicMeta = require('music-metadata'); +const fileDuration = require('../helpers/file-duration'); const settings = require('../../settings'); const logger = require('sonos-discovery/lib/helpers/logger'); @@ -14,17 +14,10 @@ const DEFAULT_SETTINGS = { TextType: 'text' }; -function fileDuration(path) { - return musicMeta.parseFile(path, { duration: true }) - .then((info) => { - return Math.ceil(info.format.duration*1000); - }) -} - function polly(phrase, voiceName) { if (!settings.aws) { return Promise.resolve(); - + } // Construct a filesystem neutral filename @@ -40,7 +33,7 @@ function polly(phrase, voiceName) { const phraseHash = crypto.createHash('sha1').update(phrase).digest('hex'); const filename = `polly-${phraseHash}-${synthesizeParameters.VoiceId}.mp3`; const filepath = path.resolve(settings.webroot, 'tts', filename); - + const expectedUri = `/tts/${filename}`; try { fs.accessSync(filepath, fs.R_OK); @@ -73,4 +66,4 @@ function polly(phrase, voiceName) { }); } -module.exports = polly; \ No newline at end of file +module.exports = polly; diff --git a/lib/tts-providers/default/google.js b/lib/tts-providers/default/google.js index c203ddcc..4b9a2f32 100644 --- a/lib/tts-providers/default/google.js +++ b/lib/tts-providers/default/google.js @@ -3,17 +3,10 @@ const crypto = require('crypto'); const fs = require('fs'); const http = require('http'); const path = require('path'); -const musicMeta = require('music-metadata'); +const fileDuration = require('../../helpers/file-duration'); const settings = require('../../../settings'); const logger = require('sonos-discovery/lib/helpers/logger'); -function fileDuration(path) { - return musicMeta.parseFile(path, { duration: true }) - .then((info) => { - return Math.ceil(info.format.duration * 1000); - }) -} - function google(phrase, language) { if (!language) { language = 'en'; @@ -42,13 +35,13 @@ function google(phrase, language) { } return new Promise((resolve, reject) => { - var file = fs.createWriteStream(filepath); - var options = { + const file = fs.createWriteStream(filepath); + const options = { "headers": { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36" }, "host": "translate.google.com", "path": "/translate_tts?client=tw-ob&tl=" + language + "&q=" + encodeURIComponent(phrase) } - var callback = function (response) { + const callback = function (response) { if (response.statusCode < 300 && response.statusCode >= 200) { response.pipe(file); file.on('finish', function () { @@ -62,7 +55,6 @@ function google(phrase, language) { } http.request(options, callback).on('error', function (err) { - fs.unlink(dest); reject(err); }).end(); }) @@ -77,4 +69,4 @@ function google(phrase, language) { }); } -module.exports = google; \ No newline at end of file +module.exports = google; diff --git a/lib/tts-providers/microsoft.js b/lib/tts-providers/microsoft.js index 609a5618..bb5e4923 100644 --- a/lib/tts-providers/microsoft.js +++ b/lib/tts-providers/microsoft.js @@ -2,7 +2,7 @@ const crypto = require('crypto'); const fs = require('fs'); const path = require('path'); -const musicMeta = require('music-metadata'); +const fileDuration = require('../helpers/file-duration'); const request = require('sonos-discovery/lib/helpers/request'); const logger = require('sonos-discovery/lib/helpers/logger'); const globalSettings = require('../../settings'); @@ -42,13 +42,6 @@ function format(lang, gender, name, text) { return `${escapedText}`; } -function fileDuration(path) { - return musicMeta.parseFile(path, { duration: true }) - .then((info) => { - return Math.ceil(info.format.duration*1000); - }) -} - function microsoft(phrase, voiceName) { if (!globalSettings.microsoft || !globalSettings.microsoft.key) { return Promise.resolve(); diff --git a/lib/tts-providers/voicerss.js b/lib/tts-providers/voicerss.js index 216cf9da..ea5aa24d 100644 --- a/lib/tts-providers/voicerss.js +++ b/lib/tts-providers/voicerss.js @@ -3,20 +3,13 @@ const crypto = require('crypto'); const fs = require('fs'); const http = require('http'); const path = require('path'); -const musicMeta = require('music-metadata'); +const fileDuration = require('../helpers/file-duration'); const settings = require('../../settings'); -function fileDuration(path) { - return musicMeta.parseFile(path, { duration: true }) - .then((info) => { - return Math.ceil(info.format.duration*1000); - }) -} - function voicerss(phrase, language) { if (!settings.voicerss) { return Promise.resolve(); - + } if (!language) { @@ -24,12 +17,12 @@ function voicerss(phrase, language) { } // Use voicerss tts translation service to create a mp3 file const ttsRequestUrl = `http://api.voicerss.org/?key=${settings.voicerss}&f=22khz_16bit_mono&hl=${language}&src=${encodeURIComponent(phrase)}`; - + // Construct a filesystem neutral filename const phraseHash = crypto.createHash('sha1').update(phrase).digest('hex'); const filename = `voicerss-${phraseHash}-${language}.mp3`; const filepath = path.resolve(settings.webroot, 'tts', filename); - + const expectedUri = `/tts/${filename}`; try { fs.accessSync(filepath, fs.R_OK); @@ -43,7 +36,7 @@ function voicerss(phrase, language) { } catch (err) { console.log(`announce file for phrase "${phrase}" does not seem to exist, downloading`); } - + return new Promise((resolve, reject) => { var file = fs.createWriteStream(filepath); http.get(ttsRequestUrl, function (response) { @@ -55,7 +48,7 @@ function voicerss(phrase, language) { }); } else { reject(new Error(`Download from voicerss failed with status ${response.statusCode}, ${response.message}`)); - + } }).on('error', function (err) { fs.unlink(dest); @@ -73,4 +66,4 @@ function voicerss(phrase, language) { }); } -module.exports = voicerss; \ No newline at end of file +module.exports = voicerss; diff --git a/package-lock.json b/package-lock.json index e2cee43a..931f99ce 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "sonos-http-api", - "version": "1.4.4", + "version": "1.4.5", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -9,6 +9,29 @@ "resolved": "https://registry.npmjs.org/@types/es6-promise/-/es6-promise-0.0.33.tgz", "integrity": "sha512-HKJFVLCGrWQ/1unEw8JdaTxu6n3EUxmwTxJ6D0O1x0gD8joCsgoTWxEgevb7fp2XIogNjof3KEd+3bJoGne/nw==" }, + "acorn": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.1.2.tgz", + "integrity": "sha512-o96FZLJBPY1lvTuJylGA9Bk3t/GKPPJG8H0ydQQl01crzwJgspa4AEIq/pVTXigmK0PHVQhiAtn8WMBLL9D2WA==", + "dev": true + }, + "acorn-jsx": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", + "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", + "dev": true, + "requires": { + "acorn": "3.3.0" + }, + "dependencies": { + "acorn": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", + "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=", + "dev": true + } + } + }, "ajv": { "version": "4.11.8", "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", @@ -18,6 +41,12 @@ "json-stable-stringify": "1.0.1" } }, + "ajv-keywords": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-2.1.0.tgz", + "integrity": "sha1-opbhf3v658HOT34N5T0pyzIWLfA=", + "dev": true + }, "anesidora": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/anesidora/-/anesidora-1.2.1.tgz", @@ -27,6 +56,54 @@ "underscore": "1.8.3" } }, + "ansi-escapes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.0.0.tgz", + "integrity": "sha512-O/klc27mWNUigtv0F8NJWbLF00OcegQalkqKURWdosW08YZKi4m6CnSUSvIZG1otNJbTWhN01Hhz389DW7mvDQ==", + "dev": true + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "argparse": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.9.tgz", + "integrity": "sha1-c9g7wmP4bpf4zE9rrhsOkKfSLIY=", + "dev": true, + "requires": { + "sprintf-js": "1.0.3" + } + }, + "array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "dev": true, + "requires": { + "array-uniq": "1.0.3" + } + }, + "array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", + "dev": true + }, + "arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "dev": true + }, "asn1": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", @@ -76,6 +153,47 @@ "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz", "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=" }, + "babel-code-frame": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", + "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", + "dev": true, + "requires": { + "chalk": "1.1.3", + "esutils": "2.0.2", + "js-tokens": "3.0.2" + }, + "dependencies": { + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "2.2.1", + "escape-string-regexp": "1.0.5", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "2.1.1" + } + } + } + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, "base64-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.2.1.tgz", @@ -108,6 +226,16 @@ "hoek": "2.16.3" } }, + "brace-expansion": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", + "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=", + "dev": true, + "requires": { + "balanced-match": "1.0.0", + "concat-map": "0.0.1" + } + }, "buffer": { "version": "4.9.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", @@ -118,11 +246,84 @@ "isarray": "1.0.0" } }, + "builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", + "dev": true + }, + "caller-path": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", + "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", + "dev": true, + "requires": { + "callsites": "0.2.0" + } + }, + "callsites": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", + "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", + "dev": true + }, "caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" }, + "chalk": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.1.0.tgz", + "integrity": "sha512-LUHGS/dge4ujbXMJrnihYMcL4AoOweGnw9Tp3kQuqy1Kx5c1qKjqvMJZ6nVJPMWJtKCTN72ZogH3oeSO9g9rXQ==", + "dev": true, + "requires": { + "ansi-styles": "3.2.0", + "escape-string-regexp": "1.0.5", + "supports-color": "4.4.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", + "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", + "dev": true, + "requires": { + "color-convert": "1.9.0" + } + }, + "supports-color": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.4.0.tgz", + "integrity": "sha512-rKC3+DyXWgK0ZLKwmRsrkyHVZAjNkfzeehuFWdGGcqGDTZFH73+RH6S/RDAAxl9GusSjZSUWYLmT9N5pzXFOXQ==", + "dev": true, + "requires": { + "has-flag": "2.0.0" + } + } + } + }, + "circular-json": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", + "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", + "dev": true + }, + "cli-cursor": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", + "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "dev": true, + "requires": { + "restore-cursor": "2.0.0" + } + }, + "cli-width": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", + "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", + "dev": true + }, "cls-bluebird": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/cls-bluebird/-/cls-bluebird-1.1.3.tgz", @@ -137,6 +338,21 @@ "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" }, + "color-convert": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.0.tgz", + "integrity": "sha1-Gsz5fdc5uYO/mU1W/sj5WFNkG3o=", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, "colors": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz", @@ -150,11 +366,45 @@ "delayed-stream": "1.0.0" } }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "concat-stream": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.0.tgz", + "integrity": "sha1-CqxmL9Ur54lk1VMvaUeE5wEQrPc=", + "dev": true, + "requires": { + "inherits": "2.0.3", + "readable-stream": "2.3.3", + "typedarray": "0.0.6" + } + }, + "contains-path": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz", + "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=", + "dev": true + }, "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, + "cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "dev": true, + "requires": { + "lru-cache": "4.1.1", + "shebang-command": "1.2.0", + "which": "1.3.0" + } + }, "cryptiles": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz", @@ -183,11 +433,51 @@ } } }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "del": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", + "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", + "dev": true, + "requires": { + "globby": "5.0.0", + "is-path-cwd": "1.0.0", + "is-path-in-cwd": "1.0.0", + "object-assign": "4.1.1", + "pify": "2.3.0", + "pinkie-promise": "2.0.1", + "rimraf": "2.6.2" + } + }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" }, + "doctrine": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.0.0.tgz", + "integrity": "sha1-xz2NKQnSIpHhoAejlYBNqLZl/mM=", + "dev": true, + "requires": { + "esutils": "2.0.2", + "isarray": "1.0.0" + } + }, "ecc-jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", @@ -197,11 +487,238 @@ "jsbn": "0.1.1" } }, + "error-ex": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz", + "integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=", + "dev": true, + "requires": { + "is-arrayish": "0.2.1" + } + }, "es6-promise": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.1.1.tgz", "integrity": "sha512-OaU1hHjgJf+b0NzsxCg7NdIYERD6Hy/PEmFLTjw+b65scuisG3Kt4QoTvJ66BBkPZ581gr0kpoVzKnxniM8nng==" }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "eslint": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-4.8.0.tgz", + "integrity": "sha1-Ip7w41Tg5h2DfHqA/fuoJeGZgV4=", + "dev": true, + "requires": { + "ajv": "5.2.3", + "babel-code-frame": "6.26.0", + "chalk": "2.1.0", + "concat-stream": "1.6.0", + "cross-spawn": "5.1.0", + "debug": "3.1.0", + "doctrine": "2.0.0", + "eslint-scope": "3.7.1", + "espree": "3.5.1", + "esquery": "1.0.0", + "estraverse": "4.2.0", + "esutils": "2.0.2", + "file-entry-cache": "2.0.0", + "functional-red-black-tree": "1.0.1", + "glob": "7.1.2", + "globals": "9.18.0", + "ignore": "3.3.5", + "imurmurhash": "0.1.4", + "inquirer": "3.3.0", + "is-resolvable": "1.0.0", + "js-yaml": "3.10.0", + "json-stable-stringify": "1.0.1", + "levn": "0.3.0", + "lodash": "4.17.4", + "minimatch": "3.0.4", + "mkdirp": "0.5.1", + "natural-compare": "1.4.0", + "optionator": "0.8.2", + "path-is-inside": "1.0.2", + "pluralize": "7.0.0", + "progress": "2.0.0", + "require-uncached": "1.0.3", + "semver": "5.4.1", + "strip-ansi": "4.0.0", + "strip-json-comments": "2.0.1", + "table": "4.0.2", + "text-table": "0.2.0" + }, + "dependencies": { + "ajv": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.2.3.tgz", + "integrity": "sha1-wG9Zh3jETGsWGrr+NGa4GtGBTtI=", + "dev": true, + "requires": { + "co": "4.6.0", + "fast-deep-equal": "1.0.0", + "json-schema-traverse": "0.3.1", + "json-stable-stringify": "1.0.1" + } + } + } + }, + "eslint-config-airbnb-base": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-12.0.1.tgz", + "integrity": "sha512-j9mEf21o09cO+wg4M9cwhvu88E5OxbhC94TjcDsSXfHD25LVmhB6gjy2jUv3JH642TshKKl/HBYzVFCjMd390Q==", + "dev": true, + "requires": { + "eslint-restricted-globals": "0.1.1" + } + }, + "eslint-import-resolver-node": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.1.tgz", + "integrity": "sha512-yUtXS15gIcij68NmXmP9Ni77AQuCN0itXbCc/jWd8C6/yKZaSNXicpC8cgvjnxVdmfsosIXrjpzFq7GcDryb6A==", + "dev": true, + "requires": { + "debug": "2.6.9", + "resolve": "1.4.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + } + } + }, + "eslint-module-utils": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.1.1.tgz", + "integrity": "sha512-jDI/X5l/6D1rRD/3T43q8Qgbls2nq5km5KSqiwlyUbGo5+04fXhMKdCPhjwbqAa6HXWaMxj8Q4hQDIh7IadJQw==", + "dev": true, + "requires": { + "debug": "2.6.9", + "pkg-dir": "1.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + } + } + }, + "eslint-plugin-import": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.7.0.tgz", + "integrity": "sha512-HGYmpU9f/zJaQiKNQOVfHUh2oLWW3STBrCgH0sHTX1xtsxYlH1zjLh8FlQGEIdZSdTbUMaV36WaZ6ImXkenGxQ==", + "dev": true, + "requires": { + "builtin-modules": "1.1.1", + "contains-path": "0.1.0", + "debug": "2.6.9", + "doctrine": "1.5.0", + "eslint-import-resolver-node": "0.3.1", + "eslint-module-utils": "2.1.1", + "has": "1.0.1", + "lodash.cond": "4.5.2", + "minimatch": "3.0.4", + "read-pkg-up": "2.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "doctrine": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", + "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", + "dev": true, + "requires": { + "esutils": "2.0.2", + "isarray": "1.0.0" + } + } + } + }, + "eslint-restricted-globals": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/eslint-restricted-globals/-/eslint-restricted-globals-0.1.1.tgz", + "integrity": "sha1-NfDVy8ZMLj7WLpO0saevBbp+1Nc=", + "dev": true + }, + "eslint-scope": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.1.tgz", + "integrity": "sha1-PWPD7f2gLgbgGkUq2IyqzHzctug=", + "dev": true, + "requires": { + "esrecurse": "4.2.0", + "estraverse": "4.2.0" + } + }, + "espree": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.1.tgz", + "integrity": "sha1-DJiLirRttTEAoZVK5LqZXd0n2H4=", + "dev": true, + "requires": { + "acorn": "5.1.2", + "acorn-jsx": "3.0.1" + } + }, + "esprima": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz", + "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==", + "dev": true + }, + "esquery": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.0.tgz", + "integrity": "sha1-z7qLV9f7qT8XKYqKAGoEzaE9gPo=", + "dev": true, + "requires": { + "estraverse": "4.2.0" + } + }, + "esrecurse": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.0.tgz", + "integrity": "sha1-+pVo2Y04I/mkHZHpAtyrnqblsWM=", + "dev": true, + "requires": { + "estraverse": "4.2.0", + "object-assign": "4.1.1" + } + }, + "estraverse": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", + "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", + "dev": true + }, + "esutils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", + "dev": true + }, "events": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", @@ -212,11 +729,75 @@ "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=" }, + "external-editor": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.0.5.tgz", + "integrity": "sha512-Msjo64WT5W+NhOpQXh0nOHm+n0RfU1QUwDnKYvJ8dEJ8zlwLrqXNTv5mSUTJpepf41PDJGyhueTw2vNZW+Fr/w==", + "dev": true, + "requires": { + "iconv-lite": "0.4.19", + "jschardet": "1.5.1", + "tmp": "0.0.33" + } + }, "extsprintf": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" }, + "fast-deep-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz", + "integrity": "sha1-liVqO8l1WV6zbYLpkp0GDYk0Of8=", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "dev": true, + "requires": { + "escape-string-regexp": "1.0.5" + } + }, + "file-entry-cache": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", + "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", + "dev": true, + "requires": { + "flat-cache": "1.3.0", + "object-assign": "4.1.1" + } + }, + "find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "dev": true, + "requires": { + "path-exists": "2.1.0", + "pinkie-promise": "2.0.1" + } + }, + "flat-cache": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.0.tgz", + "integrity": "sha1-0wMLMrOBVPTjt+nHCfSQ9++XxIE=", + "dev": true, + "requires": { + "circular-json": "0.3.3", + "del": "2.2.2", + "graceful-fs": "4.1.11", + "write": "0.2.1" + } + }, "forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", @@ -242,6 +823,24 @@ "universalify": "0.1.1" } }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, "fuse.js": { "version": "2.7.4", "resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-2.7.4.tgz", @@ -262,6 +861,40 @@ } } }, + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "dev": true, + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "globals": { + "version": "9.18.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", + "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", + "dev": true + }, + "globby": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", + "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", + "dev": true, + "requires": { + "array-union": "1.0.2", + "arrify": "1.0.1", + "glob": "7.1.2", + "object-assign": "4.1.1", + "pify": "2.3.0", + "pinkie-promise": "2.0.1" + } + }, "graceful-fs": { "version": "4.1.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", @@ -281,6 +914,30 @@ "har-schema": "1.0.5" } }, + "has": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.1.tgz", + "integrity": "sha1-hGFzP1OLCDfJNh45qauelwTcLyg=", + "dev": true, + "requires": { + "function-bind": "1.1.1" + } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true, + "requires": { + "ansi-regex": "2.1.1" + } + }, + "has-flag": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", + "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", + "dev": true + }, "hawk": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", @@ -297,6 +954,12 @@ "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=" }, + "hosted-git-info": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.5.0.tgz", + "integrity": "sha512-pNgbURSuab90KbTqvRPsseaTxOJCZBD0a7t+haSN33piP9cCM4l0CqdzAif2hUqm716UovKB2ROmiabGAKVXyg==", + "dev": true + }, "html-entities": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.2.1.tgz", @@ -312,16 +975,132 @@ "sshpk": "1.13.1" } }, + "iconv-lite": { + "version": "0.4.19", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", + "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==", + "dev": true + }, "ieee754": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.8.tgz", "integrity": "sha1-vjPUCsEO8ZJnAfbwii2G+/0a0+Q=" }, + "ignore": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.5.tgz", + "integrity": "sha512-JLH93mL8amZQhh/p6mfQgVBH3M6epNq3DfsXsTSuSrInVjwyYlFE1nv2AgfRCC8PoOhM0jwQ5v8s9LgbK7yGDw==", + "dev": true + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "1.4.0", + "wrappy": "1.0.2" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "inquirer": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-3.3.0.tgz", + "integrity": "sha512-h+xtnyk4EwKvFWHrUYsWErEVR+igKtLdchu+o0Z1RL7VU/jVMFbYir2bp6bAj8efFNxWqHX0dIss6fJQ+/+qeQ==", + "dev": true, + "requires": { + "ansi-escapes": "3.0.0", + "chalk": "2.1.0", + "cli-cursor": "2.1.0", + "cli-width": "2.2.0", + "external-editor": "2.0.5", + "figures": "2.0.0", + "lodash": "4.17.4", + "mute-stream": "0.0.7", + "run-async": "2.3.0", + "rx-lite": "4.0.8", + "rx-lite-aggregates": "4.0.8", + "string-width": "2.1.1", + "strip-ansi": "4.0.0", + "through": "2.3.8" + } + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, "is-bluebird": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-bluebird/-/is-bluebird-1.0.2.tgz", "integrity": "sha1-CWQ5Bg9KpBGr7hkUOoTWpVNG1uI=" }, + "is-builtin-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", + "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", + "dev": true, + "requires": { + "builtin-modules": "1.1.1" + } + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "is-path-cwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", + "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=", + "dev": true + }, + "is-path-in-cwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.0.tgz", + "integrity": "sha1-ZHdYK4IU1gI0YJRWcAO+ip6sBNw=", + "dev": true, + "requires": { + "is-path-inside": "1.0.0" + } + }, + "is-path-inside": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.0.tgz", + "integrity": "sha1-/AbloWg/vaE95mev9xe7wQpI838=", + "dev": true, + "requires": { + "path-is-inside": "1.0.2" + } + }, + "is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", + "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", + "dev": true + }, + "is-resolvable": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.0.0.tgz", + "integrity": "sha1-jfV8YeouPFAUCNEA+wE8+NbgzGI=", + "dev": true, + "requires": { + "tryit": "1.0.3" + } + }, "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", @@ -332,6 +1111,12 @@ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, "isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", @@ -342,17 +1127,45 @@ "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz", "integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc=" }, + "js-tokens": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", + "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", + "dev": true + }, + "js-yaml": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.10.0.tgz", + "integrity": "sha512-O2v52ffjLa9VeM43J4XocZE//WT9N0IiwDa3KSHH7Tu8CtH+1qM8SIZvnsTh6v+4yFy5KUY3BHUVwjpfAWsjIA==", + "dev": true, + "requires": { + "argparse": "1.0.9", + "esprima": "4.0.0" + } + }, "jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", "optional": true }, + "jschardet": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/jschardet/-/jschardet-1.5.1.tgz", + "integrity": "sha512-vE2hT1D0HLZCLLclfBSfkfTTedhVj0fubHpJBHKwwUWX0nSbhPAfk+SG9rTX95BYNmau8rGFfCeaT6T5OW1C2A==", + "dev": true + }, "json-schema": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" }, + "json-schema-traverse": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", + "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", + "dev": true + }, "json-stable-stringify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", @@ -402,11 +1215,67 @@ } } }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, + "requires": { + "prelude-ls": "1.1.2", + "type-check": "0.3.2" + } + }, + "load-json-file": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", + "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "parse-json": "2.2.0", + "pify": "2.3.0", + "strip-bom": "3.0.0" + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "requires": { + "p-locate": "2.0.0", + "path-exists": "3.0.0" + }, + "dependencies": { + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + } + } + }, "lodash": { "version": "4.17.4", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=" }, + "lodash.cond": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/lodash.cond/-/lodash.cond-4.5.2.tgz", + "integrity": "sha1-9HGh2khr5g9quVXRcRVSPdHSVdU=", + "dev": true + }, + "lru-cache": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.1.tgz", + "integrity": "sha512-q4spe4KTfsAS1SUHLO0wz8Qiyf1+vMIAgpRYioFYDMNqKfHQbg+AVDH3i4fvpl71/P1L0dBl+fQi+P37UYf0ew==", + "dev": true, + "requires": { + "pseudomap": "1.0.2", + "yallist": "2.1.2" + } + }, "mime": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.0.tgz", @@ -425,11 +1294,49 @@ "mime-db": "1.30.0" } }, + "mimic-fn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.1.0.tgz", + "integrity": "sha1-5md4PZLonb00KBi1IwudYqZyrRg=", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "1.1.8" + } + }, "minimist": { "version": "0.0.10", "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=" }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + }, + "dependencies": { + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + } + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, "music-metadata": { "version": "0.8.4", "resolved": "https://registry.npmjs.org/music-metadata/-/music-metadata-0.8.4.tgz", @@ -442,6 +1349,18 @@ "token-types": "0.9.1" } }, + "mute-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", + "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", + "dev": true + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, "node-static": { "version": "0.7.9", "resolved": "https://registry.npmjs.org/node-static/-/node-static-0.7.9.tgz", @@ -452,11 +1371,47 @@ "optimist": "0.6.1" } }, + "normalize-package-data": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", + "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==", + "dev": true, + "requires": { + "hosted-git-info": "2.5.0", + "is-builtin-module": "1.0.0", + "semver": "5.4.1", + "validate-npm-package-license": "3.0.1" + } + }, "oauth-sign": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=" }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1.0.2" + } + }, + "onetime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "dev": true, + "requires": { + "mimic-fn": "1.1.0" + } + }, "optimist": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", @@ -466,11 +1421,159 @@ "wordwrap": "0.0.3" } }, + "optionator": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", + "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", + "dev": true, + "requires": { + "deep-is": "0.1.3", + "fast-levenshtein": "2.0.6", + "levn": "0.3.0", + "prelude-ls": "1.1.2", + "type-check": "0.3.2", + "wordwrap": "1.0.0" + }, + "dependencies": { + "wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", + "dev": true + } + } + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true + }, + "p-limit": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.1.0.tgz", + "integrity": "sha1-sH/y2aXYi+yAYDWJWiurZqJ5iLw=", + "dev": true + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "requires": { + "p-limit": "1.1.0" + } + }, + "parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "dev": true, + "requires": { + "error-ex": "1.3.1" + } + }, + "path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "dev": true, + "requires": { + "pinkie-promise": "2.0.1" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", + "dev": true + }, + "path-parse": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz", + "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=", + "dev": true + }, + "path-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", + "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", + "dev": true, + "requires": { + "pify": "2.3.0" + } + }, "performance-now": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-0.2.0.tgz", "integrity": "sha1-M+8wxcd9TqIcWlOGnZG1bY8lVeU=" }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dev": true, + "requires": { + "pinkie": "2.0.4" + } + }, + "pkg-dir": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-1.0.0.tgz", + "integrity": "sha1-ektQio1bstYp1EcFb/TpyTFM89Q=", + "dev": true, + "requires": { + "find-up": "1.1.2" + } + }, + "pluralize": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-7.0.0.tgz", + "integrity": "sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow==", + "dev": true + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true + }, + "process-nextick-args": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", + "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=", + "dev": true + }, + "progress": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.0.tgz", + "integrity": "sha1-ihvjZr+Pwj2yvSPxDG/pILQ4nR8=", + "dev": true + }, + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", + "dev": true + }, "punycode": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", @@ -486,6 +1589,53 @@ "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" }, + "read-pkg": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", + "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", + "dev": true, + "requires": { + "load-json-file": "2.0.0", + "normalize-package-data": "2.4.0", + "path-type": "2.0.0" + } + }, + "read-pkg-up": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", + "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", + "dev": true, + "requires": { + "find-up": "2.1.0", + "read-pkg": "2.0.0" + }, + "dependencies": { + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "2.0.0" + } + } + } + }, + "readable-stream": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", + "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", + "dev": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "1.0.7", + "safe-buffer": "5.1.1", + "string_decoder": "1.0.3", + "util-deprecate": "1.0.2" + } + }, "request": { "version": "2.81.0", "resolved": "https://registry.npmjs.org/request/-/request-2.81.0.tgz", @@ -533,6 +1683,74 @@ } } }, + "require-uncached": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", + "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", + "dev": true, + "requires": { + "caller-path": "0.1.0", + "resolve-from": "1.0.1" + } + }, + "resolve": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.4.0.tgz", + "integrity": "sha512-aW7sVKPufyHqOmyyLzg/J+8606v5nevBgaliIlV7nUpVMsDnoBGV/cbSLNjZAg9q0Cfd/+easKVKQ8vOu8fn1Q==", + "dev": true, + "requires": { + "path-parse": "1.0.5" + } + }, + "resolve-from": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", + "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=", + "dev": true + }, + "restore-cursor": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", + "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", + "dev": true, + "requires": { + "onetime": "2.0.1", + "signal-exit": "3.0.2" + } + }, + "rimraf": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", + "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", + "dev": true, + "requires": { + "glob": "7.1.2" + } + }, + "run-async": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", + "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", + "dev": true, + "requires": { + "is-promise": "2.1.0" + } + }, + "rx-lite": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-4.0.8.tgz", + "integrity": "sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ=", + "dev": true + }, + "rx-lite-aggregates": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz", + "integrity": "sha1-dTuHqJoRyVRnxKwWJsTvxOBcZ74=", + "dev": true, + "requires": { + "rx-lite": "4.0.8" + } + }, "safe-buffer": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", @@ -543,11 +1761,47 @@ "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", "integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o=" }, + "semver": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz", + "integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg==", + "dev": true + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "requires": { + "shebang-regex": "1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true + }, "shimmer": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.1.0.tgz", "integrity": "sha1-l9c3cTf/u6tCVSLkKf4KqJpIizU=" }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "dev": true + }, + "slice-ansi": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-1.0.0.tgz", + "integrity": "sha512-POqxBK6Lb3q6s047D/XsDVNPnF9Dl8JSaqe9h9lURl0OdNqy/ujDrOiIHtsqXMGbWWTIomRzAMaTyawAU//Reg==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "2.0.0" + } + }, "sntp": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz", @@ -571,6 +1825,33 @@ } } }, + "spdx-correct": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-1.0.2.tgz", + "integrity": "sha1-SzBz2TP/UfORLwOsVRlJikFQ20A=", + "dev": true, + "requires": { + "spdx-license-ids": "1.2.2" + } + }, + "spdx-expression-parse": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-1.0.4.tgz", + "integrity": "sha1-m98vIOH0DtRH++JzJmGR/O1RYmw=", + "dev": true + }, + "spdx-license-ids": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz", + "integrity": "sha1-yd96NCRZSt5r0RkA1ZZpbcBrrFc=", + "dev": true + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, "sshpk": { "version": "1.13.1", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.1.tgz", @@ -593,11 +1874,59 @@ } } }, + "string_decoder": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "dev": true, + "requires": { + "safe-buffer": "5.1.1" + } + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "2.0.0", + "strip-ansi": "4.0.0" + } + }, "stringstream": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=" }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "3.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + } + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true + }, "strtok3": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-1.3.2.tgz", @@ -610,11 +1939,66 @@ "token-types": "0.9.1" } }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + }, + "table": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/table/-/table-4.0.2.tgz", + "integrity": "sha512-UUkEAPdSGxtRpiV9ozJ5cMTtYiqz7Ni1OGqLXRCynrvzdtR1p+cfOWe2RJLwvUG8hNanaSRjecIqwOjqeatDsA==", + "dev": true, + "requires": { + "ajv": "5.2.3", + "ajv-keywords": "2.1.0", + "chalk": "2.1.0", + "lodash": "4.17.4", + "slice-ansi": "1.0.0", + "string-width": "2.1.1" + }, + "dependencies": { + "ajv": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.2.3.tgz", + "integrity": "sha1-wG9Zh3jETGsWGrr+NGa4GtGBTtI=", + "dev": true, + "requires": { + "co": "4.6.0", + "fast-deep-equal": "1.0.0", + "json-schema-traverse": "0.3.1", + "json-stable-stringify": "1.0.1" + } + } + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, "then-read-stream": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/then-read-stream/-/then-read-stream-1.0.3.tgz", "integrity": "sha1-lwR4ZiqR382+APnxNfF7zPKQcf8=" }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "requires": { + "os-tmpdir": "1.0.2" + } + }, "token-types": { "version": "0.9.1", "resolved": "https://registry.npmjs.org/token-types/-/token-types-0.9.1.tgz", @@ -628,6 +2012,12 @@ "punycode": "1.4.1" } }, + "tryit": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tryit/-/tryit-1.0.3.tgz", + "integrity": "sha1-OTvnMKlEb9Hq1tpZoBQwjzbCics=", + "dev": true + }, "tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -642,6 +2032,21 @@ "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", "optional": true }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, + "requires": { + "prelude-ls": "1.1.2" + } + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true + }, "underscore": { "version": "1.8.3", "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz", @@ -668,11 +2073,27 @@ } } }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, "uuid": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz", "integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g==" }, + "validate-npm-package-license": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz", + "integrity": "sha1-KAS6vnEq0zeUWaz74kdGqywwP7w=", + "dev": true, + "requires": { + "spdx-correct": "1.0.2", + "spdx-expression-parse": "1.0.4" + } + }, "verror": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", @@ -695,11 +2116,35 @@ "resolved": "https://registry.npmjs.org/wav-file-info/-/wav-file-info-0.0.8.tgz", "integrity": "sha1-aAp160w0a34/RX55AqexmDUG5N4=" }, + "which": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz", + "integrity": "sha512-xcJpopdamTuY5duC/KnTTNBraPK54YwpenP4lzxU8H91GudWpFv38u0CKjclE1Wi2EH2EDz5LRcHcKbCIzqGyg==", + "dev": true, + "requires": { + "isexe": "2.0.0" + } + }, "wordwrap": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=" }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "write": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", + "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", + "dev": true, + "requires": { + "mkdirp": "0.5.1" + } + }, "xml-flow": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/xml-flow/-/xml-flow-1.0.1.tgz", @@ -724,6 +2169,12 @@ "requires": { "lodash": "4.17.4" } + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", + "dev": true } } } diff --git a/package.json b/package.json index a96bb235..91f09e94 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,10 @@ { "name": "sonos-http-api", - "version": "1.4.5", + "version": "1.4.6", "description": "A simple node app for controlling a Sonos system with basic HTTP requests", "scripts": { - "start": "node server.js" + "start": "node server.js", + "lint": "eslint lib" }, "author": "Jimmy Shimizu ", "repository": { @@ -29,5 +30,10 @@ "npm": "^2.0.0" }, "main": "lib/sonos-http-api.js", - "license": "MIT" + "license": "MIT", + "devDependencies": { + "eslint": "^4.8.0", + "eslint-config-airbnb-base": "^12.0.1", + "eslint-plugin-import": "^2.7.0" + } } From e8c49e796b89983cb283abd5276e60c03a969d68 Mon Sep 17 00:00:00 2001 From: Connor Knabe Date: Fri, 6 Oct 2017 03:58:54 -0500 Subject: [PATCH 052/123] minor typo fix (#556) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ba77981b..931ecb2b 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Feel free to use it as you please. Consider donating if you want to support further development. Reach out on the gitter chat if you have issues getting it to run, instead of creating new issues, thank you! -If you are also looking for cloud control (ifttt, public webhooks etc), see the [bronos-client](http://www.bronos.net) project! That pi image also contain an installation of this http-api. +If you are also looking for cloud control (ifttt, public webhooks etc), see the [bronos-client](http://www.bronos.net) project! That pi image also contains an installation of this http-api. SONOS HTTP API ============== From 98882496fa63b3d0e63de71938369606eeda3d0c Mon Sep 17 00:00:00 2001 From: John LaBarge Date: Sat, 7 Oct 2017 09:19:58 -0500 Subject: [PATCH 053/123] Fix: pauseall action #557 (#558) --- lib/actions/pauseall.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/actions/pauseall.js b/lib/actions/pauseall.js index 233c6226..9312162e 100644 --- a/lib/actions/pauseall.js +++ b/lib/actions/pauseall.js @@ -1,4 +1,5 @@ 'use strict'; +const logger = require('sonos-discovery/lib/helpers/logger'); var pausedPlayers = []; function pauseAll(player, values) { @@ -61,4 +62,4 @@ function doResumeAll(system) { module.exports = function (api) { api.registerAction('pauseall', pauseAll); api.registerAction('resumeall', resumeAll); -} \ No newline at end of file +} From 9a45ef97357d7ab56353ce18a3940754eef69feb Mon Sep 17 00:00:00 2001 From: klaas Date: Tue, 3 Oct 2017 18:49:56 +0200 Subject: [PATCH 054/123] corrected the name of an undefined closure: onTransportChange --- lib/helpers/single-player-announcement.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/helpers/single-player-announcement.js b/lib/helpers/single-player-announcement.js index b84f6e51..50117adf 100644 --- a/lib/helpers/single-player-announcement.js +++ b/lib/helpers/single-player-announcement.js @@ -107,7 +107,7 @@ function singlePlayerAnnouncement(player, uri, volume, duration) { return resolve(); } - player.once('transport-state', onTransportChange); + player.once('transport-state', transportChange); }; setTimeout(() => { player.once('transport-state', transportChange); From 24da92bcc92ec23b1131985afc27b73396de9ff0 Mon Sep 17 00:00:00 2001 From: klaas Date: Tue, 3 Oct 2017 23:21:28 +0200 Subject: [PATCH 055/123] macOS text to speech (tts) --- lib/tts-providers/macOs.js | 62 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 lib/tts-providers/macOs.js diff --git a/lib/tts-providers/macOs.js b/lib/tts-providers/macOs.js new file mode 100644 index 00000000..5087eb81 --- /dev/null +++ b/lib/tts-providers/macOs.js @@ -0,0 +1,62 @@ +'use strict'; +const crypto = require('crypto'); +const fs = require('fs'); +const http = require('http'); +const path = require('path'); +const fileDuration = require('../helpers/file-duration'); +const settings = require('../../settings'); +const logger = require('sonos-discovery/lib/helpers/logger'); +var exec = require('child_process').exec; + +function mac_os_say(phrase, voice) { + if (!voice) { + voice = 'Alex'; + } + + // Construct a filesystem neutral filename + const phraseHash = crypto.createHash('sha1').update(phrase).digest('hex'); + const filename = `macOS-${phraseHash}-${voice}.m4a`; + const filepath = path.resolve(settings.webroot, 'tts', filename); + + const expectedUri = `/tts/${filename}`; + + try { + fs.accessSync(filepath, fs.R_OK); + return fileDuration(filepath) + .then((duration) => { + return { + duration, + uri: expectedUri + }; + }); + } catch (err) { + logger.info(`announce file for phrase "${phrase}" does not seem to exist, downloading`); + } + + return new Promise((resolve, reject) => { + exec('say -v ' + voice + ' "' + phrase + '" -o ' + filepath, + function (error, stdout, stderr) { + // console.log('say stdout: ' + stdout); + // console.log('say stderr: ' + stderr); + + if (error !== null) { + // console.log('exec error: ' + error); + reject(error); + } else { + resolve(expectedUri); + } + }); + + }) + .then(() => { + return fileDuration(filepath); + }) + .then((duration) => { + return { + duration, + uri: expectedUri + }; + }); +} + +module.exports = mac_os_say; From 8edbbfd20af732375361d44266650d7c5a373674 Mon Sep 17 00:00:00 2001 From: klaas Date: Thu, 5 Oct 2017 00:29:34 +0200 Subject: [PATCH 056/123] added section to README.md added configuration setting corrected spelling of function name --- README.md | 37 +++++++++++++++++++++++ lib/tts-providers/{macOs.js => mac-os.js} | 31 ++++++++++++------- 2 files changed, 57 insertions(+), 11 deletions(-) rename lib/tts-providers/{macOs.js => mac-os.js} (64%) diff --git a/README.md b/README.md index 931ecb2b..1f318e19 100644 --- a/README.md +++ b/README.md @@ -384,6 +384,7 @@ Experimental support for TTS. Today the following providers are available: * Microsoft Cognitive Services (Bing Text to Speech API) * AWS Polly * Google (default) +* macOS say command It will use the one you configure in settings.json. If you define settings for multiple TTS services, it will not be guaranteed which one it will choose! @@ -654,6 +655,42 @@ Action is: /[Room name]/say/[phrase][/[language_code]][/[announce volume]] /sayall/[phrase][/[language_code]][/[announce volume]] +#### macOS say command +On macOS the "say" command can be used for text to speech. If your installation runs on macOS you can activate the system tts by configuring a default voice. + +```json + { + "macDefaultVoice" : "Alex" + } +``` + +Action is: + + /[Room name]/say/[phrase][/[voice]][/[announce volume]] + /sayall/[phrase][/[voice]][/[announce volume]] + +Example: + + /Office/say/Hello, dinner is ready + /Office/say/Hello, dinner is ready/Agnes + /Office/say/Guten morgen/Anna + /sayall/Hello, dinner is ready + /Office/say/Hello, dinner is ready/90 + /Office/say/Guten morgen/Anna/90 + +Supported voices are: + +Alex, Alice, Alva, Amelie, Anna, Carmit, Damayanti, Daniel, Diego, Ellen, Fiona, Fred, Ioana, Joana, Jorge, Juan, Kanya, Karen, Kyoko, Laura, Lekha, Luca, Luciana, Maged, Mariska, Mei-Jia, Melina, Milena, Moira, Monica, Nora, Paulina, Samantha, Sara, Satu, Sin-ji, Tessa, Thomas, Ting-Ting, Veena, Victoria, Xander, Yelda, Yuna, Yuri, Zosia, Zuzana + +A list of available voices can print by this command: +``` + say -v '?' +``` + +See also https://gist.github.com/mculp/4b95752e25c456d425c6 and https://stackoverflow.com/questions/1489800/getting-list-of-mac-text-to-speech-voices-programmatically + +To download more voices go to: System Preferences -> Accessibility -> Speech -> System Voice + Line-in ------- diff --git a/lib/tts-providers/macOs.js b/lib/tts-providers/mac-os.js similarity index 64% rename from lib/tts-providers/macOs.js rename to lib/tts-providers/mac-os.js index 5087eb81..bea93b5b 100644 --- a/lib/tts-providers/macOs.js +++ b/lib/tts-providers/mac-os.js @@ -8,18 +8,23 @@ const settings = require('../../settings'); const logger = require('sonos-discovery/lib/helpers/logger'); var exec = require('child_process').exec; -function mac_os_say(phrase, voice) { - if (!voice) { - voice = 'Alex'; +function macSay(phrase, voice) { + if (!settings.macDefaultVoice) { + return Promise.resolve(); + } + + var selectedVoice = settings.macDefaultVoice; + if( voice != undefined ) { + selectedVoice = voice; } // Construct a filesystem neutral filename const phraseHash = crypto.createHash('sha1').update(phrase).digest('hex'); - const filename = `macOS-${phraseHash}-${voice}.m4a`; + const filename = `macSay-${phraseHash}-${selectedVoice}.m4a`; const filepath = path.resolve(settings.webroot, 'tts', filename); const expectedUri = `/tts/${filename}`; - + try { fs.accessSync(filepath, fs.R_OK); return fileDuration(filepath) @@ -34,13 +39,17 @@ function mac_os_say(phrase, voice) { } return new Promise((resolve, reject) => { - exec('say -v ' + voice + ' "' + phrase + '" -o ' + filepath, + // + // For more information on the "say" command, type "man say" in Terminal + // or go to + // https://developer.apple.com/legacy/library/documentation/Darwin/Reference/ManPages/man1/say.1.html + // + // The list of available voices can be configured in + // System Preferences -> Accessibility -> Speech -> System Voice + // + exec(`say -v ${selectedVoice} "${phrase}" -o ${filepath}`, function (error, stdout, stderr) { - // console.log('say stdout: ' + stdout); - // console.log('say stderr: ' + stderr); - if (error !== null) { - // console.log('exec error: ' + error); reject(error); } else { resolve(expectedUri); @@ -59,4 +68,4 @@ function mac_os_say(phrase, voice) { }); } -module.exports = mac_os_say; +module.exports = macSay; From 24ad4df7b0ec60a8de4929021ff01096c72b22ac Mon Sep 17 00:00:00 2001 From: klaas Date: Thu, 5 Oct 2017 00:32:31 +0200 Subject: [PATCH 057/123] spelling --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1f318e19..ab87672e 100644 --- a/README.md +++ b/README.md @@ -656,7 +656,7 @@ Action is: /sayall/[phrase][/[language_code]][/[announce volume]] #### macOS say command -On macOS the "say" command can be used for text to speech. If your installation runs on macOS you can activate the system tts by configuring a default voice. +On macOS the "say" command can be used for text to speech. If your installation runs on macOS you can activate the system tts by configuring a default voice in settings.json: ```json { @@ -682,7 +682,7 @@ Supported voices are: Alex, Alice, Alva, Amelie, Anna, Carmit, Damayanti, Daniel, Diego, Ellen, Fiona, Fred, Ioana, Joana, Jorge, Juan, Kanya, Karen, Kyoko, Laura, Lekha, Luca, Luciana, Maged, Mariska, Mei-Jia, Melina, Milena, Moira, Monica, Nora, Paulina, Samantha, Sara, Satu, Sin-ji, Tessa, Thomas, Ting-Ting, Veena, Victoria, Xander, Yelda, Yuna, Yuri, Zosia, Zuzana -A list of available voices can print by this command: +A list of available voices can be printed by this command: ``` say -v '?' ``` From 8b9a36a8a3638c29c27a51c76f12ecf49c1f5c5d Mon Sep 17 00:00:00 2001 From: klaas Date: Fri, 6 Oct 2017 10:38:17 +0200 Subject: [PATCH 058/123] support for 'rate' option --- lib/tts-providers/mac-os.js | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/lib/tts-providers/mac-os.js b/lib/tts-providers/mac-os.js index bea93b5b..53e82686 100644 --- a/lib/tts-providers/mac-os.js +++ b/lib/tts-providers/mac-os.js @@ -9,18 +9,22 @@ const logger = require('sonos-discovery/lib/helpers/logger'); var exec = require('child_process').exec; function macSay(phrase, voice) { - if (!settings.macDefaultVoice) { + if (!settings.macSay) { return Promise.resolve(); } - var selectedVoice = settings.macDefaultVoice; - if( voice != undefined ) { + var selcetedRate = settings.macSay.rate; + if( !selcetedRate ) { + selcetedRate = "default"; + } + var selectedVoice = settings.macSay.voice; + if( voice ) { selectedVoice = voice; } // Construct a filesystem neutral filename const phraseHash = crypto.createHash('sha1').update(phrase).digest('hex'); - const filename = `macSay-${phraseHash}-${selectedVoice}.m4a`; + const filename = `macSay-${phraseHash}-${selcetedRate}-${selectedVoice}.m4a`; const filepath = path.resolve(settings.webroot, 'tts', filename); const expectedUri = `/tts/${filename}`; @@ -47,7 +51,17 @@ function macSay(phrase, voice) { // The list of available voices can be configured in // System Preferences -> Accessibility -> Speech -> System Voice // - exec(`say -v ${selectedVoice} "${phrase}" -o ${filepath}`, + + var execCommand = `say "${phrase}" -o ${filepath}`; + if( selectedVoice && selcetedRate != "default" ) { + execCommand = `say -r ${selcetedRate} -v ${selectedVoice} "${phrase}" -o ${filepath}`; + } else if ( selectedVoice ) { + execCommand = `say -v ${selectedVoice} "${phrase}" -o ${filepath}`; + } else if ( selcetedRate != "default" ) { + execCommand = `say -r ${selcetedRate} "${phrase}" -o ${filepath}`; + } + + exec(execCommand, function (error, stdout, stderr) { if (error !== null) { reject(error); From b5f3b238b23cba730c00f4acd3491be680b011bf Mon Sep 17 00:00:00 2001 From: klaas Date: Fri, 6 Oct 2017 10:48:30 +0200 Subject: [PATCH 059/123] updated documentation to include 'rate' setting --- README.md | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index ab87672e..c4495605 100644 --- a/README.md +++ b/README.md @@ -656,12 +656,23 @@ Action is: /sayall/[phrase][/[language_code]][/[announce volume]] #### macOS say command -On macOS the "say" command can be used for text to speech. If your installation runs on macOS you can activate the system tts by configuring a default voice in settings.json: +On macOS the "say" command can be used for text to speech. If your installation runs on macOS you can activate the system TTS by giving an empty configuration: ```json - { - "macDefaultVoice" : "Alex" - } +{ + "macSay": {} +} +``` + +Or you can provide a default voice and a speech rate: + +```json +{ + "macSay": { + "voice" : "Alex", + "rate": 90 + } +} ``` Action is: From 047fb1ea6c3d64e0d033dd5d0ca40864827b0a2a Mon Sep 17 00:00:00 2001 From: Jimmy Shimizu Date: Tue, 17 Oct 2017 23:50:23 +0200 Subject: [PATCH 060/123] feat: Add say/clip preset actions --- README.md | 2 + lib/actions/clippreset.js | 30 ++++++++ lib/actions/preset.js | 82 +-------------------- lib/actions/saypreset.js | 40 ++++++++++ lib/helpers/preset-announcement.js | 114 +++++++++++++++++++++++++++++ lib/presets-loader.js | 87 ++++++++++++++++++++++ package.json | 2 +- presets/example.json | 2 +- 8 files changed, 277 insertions(+), 82 deletions(-) create mode 100644 lib/actions/clippreset.js create mode 100644 lib/actions/saypreset.js create mode 100644 lib/helpers/preset-announcement.js create mode 100644 lib/presets-loader.js diff --git a/README.md b/README.md index c4495605..05997c7b 100644 --- a/README.md +++ b/README.md @@ -91,12 +91,14 @@ The actions supported as of today: * resumeall (will resume the ones that was pause on the pauseall call. Useful for doorbell, phone calls, etc. Optional timeout) * say * sayall +* saypreset * queue * clearqueue * sleep (values in seconds) * linein (only analog linein, not PLAYBAR yet) * clip (announce custom mp3 clip) * clipall +* clippreset * join / leave (Grouping actions) * sub (on/off/gain/crossover/polarity) See SUB section for more info * nightmode (on/off, PLAYBAR only) diff --git a/lib/actions/clippreset.js b/lib/actions/clippreset.js new file mode 100644 index 00000000..dfa0e3f0 --- /dev/null +++ b/lib/actions/clippreset.js @@ -0,0 +1,30 @@ +'use strict'; +const path = require('path'); +const settings = require('../../settings'); +const presetAnnouncement = require('../helpers/preset-announcement'); +const fileDuration = require('../helpers/file-duration'); +const presets = require('../presets-loader'); + +let port; +const LOCAL_PATH_LOCATION = path.join(settings.webroot, 'clips'); + +function playClipOnPreset(player, values) { + const presetName = decodeURIComponent(values[0]); + const clipFileName = decodeURIComponent(values[1]); + + const preset = presets[presetName]; + + if (!preset) { + return Promise.reject(new Error(`No preset named ${presetName} could be found`)); + } + + return fileDuration(path.join(LOCAL_PATH_LOCATION, clipFileName)) + .then((duration) => { + return presetAnnouncement(player.system, `http://${player.system.localEndpoint}:${port}/clips/${clipFileName}`, preset, duration); + }); +} + +module.exports = function (api) { + port = api.getPort(); + api.registerAction('clippreset', playClipOnPreset); +} diff --git a/lib/actions/preset.js b/lib/actions/preset.js index c48aba3c..93252212 100644 --- a/lib/actions/preset.js +++ b/lib/actions/preset.js @@ -1,14 +1,8 @@ 'use strict'; const fs = require('fs'); const util = require('util'); -const path = require('path'); const logger = require('sonos-discovery/lib/helpers/logger'); -const tryLoadJson = require('../helpers/try-load-json'); -const settings = require('../../settings'); - -const PRESETS_PATH = settings.presetDir; -const PRESETS_FILENAME = `${__dirname}/../../presets.json`; -let presets = {}; +const presets = require('../presets-loader'); function presetsAction(player, values) { const value = decodeURIComponent(values[0]); @@ -27,78 +21,6 @@ function presetsAction(player, values) { } } -function readPresetsFromDir(presets, presetPath) { - let files; - try { - files = fs.readdirSync(presetPath); - } catch (e) { - logger.warn(`Could not find dir ${presetPath}, are you sure it exists?`); - logger.warn(e.message); - return; - } - - files.map((name) => { - let fullPath = path.join(presetPath, name); - return { - name, - fullPath, - stat: fs.statSync(fullPath) - }; - }).filter((file) => { - return !file.stat.isDirectory() && !file.name.startsWith('.') && file.name.endsWith('.json'); - }).forEach((file) => { - const presetName = file.name.replace(/\.json/i, ''); - const preset = tryLoadJson(file.fullPath); - if (Object.keys(preset).length === 0) { - logger.warn(`could not parse preset file ${file.name}, please make sure syntax conforms with JSON5.`); - return; - } - - presets[presetName] = preset; - }); - -} - -function readPresetsFromFile(presets, filename) { - try { - const presetStat = fs.statSync(filename); - if (!presetStat.isFile()) { - return; - } - - const filePresets = require(filename); - Object.keys(filePresets).forEach(presetName => { - presets[presetName] = filePresets[presetName]; - }); - - logger.warn('You are using a presets.json file! ' + - 'Consider migrating your presets into the presets/ ' + - 'folder instead, and enjoy auto-reloading of presets when you change them'); - } catch (err) { - logger.debug(`no presets.json file exists, skipping`); - } -} - -function initPresets() { - presets = {}; - readPresetsFromFile(presets, PRESETS_FILENAME); - readPresetsFromDir(presets, PRESETS_PATH); - - logger.info('Presets loaded:', util.inspect(presets, { depth: null })); - -} - module.exports = function (api) { - initPresets(); - let watchTimeout; - try { - fs.watch(PRESETS_PATH, { persistent: false }, () => { - clearTimeout(watchTimeout); - watchTimeout = setTimeout(initPresets, 200); - }); - } catch (e) { - logger.warn(`Could not start watching dir ${PRESETS_PATH}, will not auto reload any presets. Make sure the dir exists`); - logger.warn(e.message); - } api.registerAction('preset', presetsAction); -} \ No newline at end of file +}; \ No newline at end of file diff --git a/lib/actions/saypreset.js b/lib/actions/saypreset.js new file mode 100644 index 00000000..cb408645 --- /dev/null +++ b/lib/actions/saypreset.js @@ -0,0 +1,40 @@ +'use strict'; +const tryDownloadTTS = require('../helpers/try-download-tts'); +const presetAnnouncement = require('../helpers/preset-announcement'); +const presets = require('../presets-loader'); + +let port; +let system; + +function sayPreset(player, values) { + let text; + const presetName = decodeURIComponent(values[0]); + + const preset = presets[presetName]; + + if (!preset) { + return Promise.reject(new Error(`No preset named ${presetName} could be found`)); + } + + try { + text = decodeURIComponent(values[1]); + } catch (err) { + if (err instanceof URIError) { + err.message = `The encoded phrase ${values[0]} could not be URI decoded. Make sure your url encoded values (%xx) are within valid ranges. xx should be hexadecimal representations`; + } + return Promise.reject(err); + } + + const language = values[2]; + + return tryDownloadTTS(text, language) + .then((result) => { + return presetAnnouncement(player.system, `http://${player.system.localEndpoint}:${port}${result.uri}`, preset, result.duration); + }) + +} + +module.exports = function (api) { + port = api.getPort(); + api.registerAction('saypreset', sayPreset); +}; diff --git a/lib/helpers/preset-announcement.js b/lib/helpers/preset-announcement.js new file mode 100644 index 00000000..345a8ef4 --- /dev/null +++ b/lib/helpers/preset-announcement.js @@ -0,0 +1,114 @@ +'use strict'; +const logger = require('sonos-discovery/lib/helpers/logger'); +const isRadioOrLineIn = require('../helpers/is-radio-or-line-in'); + +function saveAll(system) { + const backupPresets = system.zones.map((zone) => { + const coordinator = zone.coordinator; + const state = coordinator.state; + const preset = { + players: [ + { roomName: coordinator.roomName, volume: state.volume } + ], + state: state.playbackState, + uri: coordinator.avTransportUri, + metadata: coordinator.avTransportUriMetadata, + playMode: { + repeat: state.playMode.repeat + } + }; + + if (!isRadioOrLineIn(preset.uri)) { + preset.trackNo = state.trackNo; + preset.elapsedTime = state.elapsedTime; + } + + zone.members.forEach(function (player) { + if (coordinator.uuid != player.uuid) + preset.players.push({ roomName: player.roomName, volume: player.state.volume }); + }); + + return preset; + + }); + + logger.trace('backup presets', backupPresets); + return backupPresets.sort((a, b) => { + return a.players.length < b.players.length; + }); +} + +function announcePreset(system, uri, preset, duration) { + let abortTimer; + + // Save all players + var backupPresets = saveAll(system); + + const simplifiedPreset = { + uri, + players: preset.players, + playMode: preset.playMode, + pauseOthers: true, + state: 'STOPPED' + }; + + function hasReachedCorrectTopology(zones) { + return zones.some(group => + group.members.length === preset.players.length && + group.coordinator.roomName === preset.players[0].roomName); + } + + const oneGroupPromise = new Promise((resolve) => { + const onTopologyChanged = (topology) => { + if (hasReachedCorrectTopology(topology)) { + return resolve(); + } + // Not one group yet, continue listening + system.once('topology-change', onTopologyChanged); + }; + + system.once('topology-change', onTopologyChanged); + }); + + const restoreTimeout = duration + 2000; + const coordinator = system.getPlayer(preset.players[0].roomName); + return system.applyPreset(simplifiedPreset) + .then(() => { + if (hasReachedCorrectTopology(system.zones)) return; + return oneGroupPromise; + }) + .then(() => { + coordinator.play(); + return new Promise((resolve) => { + const transportChange = (state) => { + logger.debug(`Player changed to state ${state.playbackState}`); + if (state.playbackState === 'STOPPED') { + return resolve(); + } + + coordinator.once('transport-state', transportChange); + }; + setTimeout(() => { + coordinator.once('transport-state', transportChange); + }, duration / 2); + logger.debug(`Setting restore timer for ${restoreTimeout} ms`); + abortTimer = setTimeout(resolve, restoreTimeout); + }); + }) + .then(() => { + clearTimeout(abortTimer); + }) + .then(() => { + return backupPresets.reduce((promise, preset) => { + logger.trace('Restoring preset', preset); + return promise.then(() => system.applyPreset(preset)); + }, Promise.resolve()); + }) + .catch((err) => { + logger.error(err.stack); + throw err; + }); + +} + +module.exports = announcePreset; \ No newline at end of file diff --git a/lib/presets-loader.js b/lib/presets-loader.js new file mode 100644 index 00000000..ec296ba3 --- /dev/null +++ b/lib/presets-loader.js @@ -0,0 +1,87 @@ +const fs = require('fs'); +const util = require('util'); +const path = require('path'); +const logger = require('sonos-discovery/lib/helpers/logger'); +const tryLoadJson = require('./helpers/try-load-json'); +const settings = require('../settings'); + +const PRESETS_PATH = settings.presetDir; +const PRESETS_FILENAME = `${__dirname}/../presets.json`; +const presets = {}; + +function readPresetsFromDir(presets, presetPath) { + let files; + try { + files = fs.readdirSync(presetPath); + } catch (e) { + logger.warn(`Could not find dir ${presetPath}, are you sure it exists?`); + logger.warn(e.message); + return; + } + + files.map((name) => { + let fullPath = path.join(presetPath, name); + return { + name, + fullPath, + stat: fs.statSync(fullPath) + }; + }).filter((file) => { + return !file.stat.isDirectory() && !file.name.startsWith('.') && file.name.endsWith('.json'); + }).forEach((file) => { + const presetName = file.name.replace(/\.json/i, ''); + const preset = tryLoadJson(file.fullPath); + if (Object.keys(preset).length === 0) { + logger.warn(`could not parse preset file ${file.name}, please make sure syntax conforms with JSON5.`); + return; + } + + presets[presetName] = preset; + }); + +} + +function readPresetsFromFile(presets, filename) { + try { + const presetStat = fs.statSync(filename); + if (!presetStat.isFile()) { + return; + } + + const filePresets = require(filename); + Object.keys(filePresets).forEach(presetName => { + presets[presetName] = filePresets[presetName]; + }); + + logger.warn('You are using a presets.json file! ' + + 'Consider migrating your presets into the presets/ ' + + 'folder instead, and enjoy auto-reloading of presets when you change them'); + } catch (err) { + logger.debug(`no presets.json file exists, skipping`); + } +} + +function initPresets() { + Object.keys(presets).forEach(presetName => { + delete presets[presetName]; + }); + readPresetsFromFile(presets, PRESETS_FILENAME); + readPresetsFromDir(presets, PRESETS_PATH); + + logger.info('Presets loaded:', util.inspect(presets, { depth: null })); + +} + +initPresets(); +let watchTimeout; +try { + fs.watch(PRESETS_PATH, { persistent: false }, () => { + clearTimeout(watchTimeout); + watchTimeout = setTimeout(initPresets, 200); + }); +} catch (e) { + logger.warn(`Could not start watching dir ${PRESETS_PATH}, will not auto reload any presets. Make sure the dir exists`); + logger.warn(e.message); +} + +module.exports = presets; \ No newline at end of file diff --git a/package.json b/package.json index 91f09e94..294a9ef4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sonos-http-api", - "version": "1.4.6", + "version": "1.5.0", "description": "A simple node app for controlling a Sonos system with basic HTTP requests", "scripts": { "start": "node server.js", diff --git a/presets/example.json b/presets/example.json index ec7afcde..420c2540 100644 --- a/presets/example.json +++ b/presets/example.json @@ -27,5 +27,5 @@ "crossfade": false }, "pauseOthers": false, - "favorite": "My example favorite" + } From 6e0f89c1f30320aadc868a194fa6ae6f3feaeec3 Mon Sep 17 00:00:00 2001 From: Eric Stiens Date: Wed, 18 Oct 2017 14:05:18 -0500 Subject: [PATCH 061/123] Adds projects built on the API to the README (#559) I thought it might be nice to have a list of projects built on top of this API and what languages they use for people looking for examples. I included a few I found, anyone is welcome to add more! --- README.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/README.md b/README.md index 05997c7b..9168ec3a 100644 --- a/README.md +++ b/README.md @@ -972,3 +972,31 @@ TCP, port 80/443 (for looking up hig res cover arts on various music services) The UDP traffic is a mixture of multicast (outgoing), broadcast (outgoing) and unicast (incoming). The multicast address is 239.255.255.250, the broadcast is 255.255.255.255 and the unicast is from the Sonos players. If port 3500 is occupied while trying to bind it, it will try using 3501, 3502, 3503 etc. You would need to adjust your firewall rules accordingly, if running multiple instances of this software, or any other software utilizing these ports. + +### Projects built with this API + +**Alexa For Sonos (Alexa Skills)** + +Amazon Alexa voice layer on top of the amazing NodeJS component +https://github.com/hypermoose/AlexaForSonos + +**JukeBot (Ruby)** + +A Slack bot that can control a Sonos instance. Custom spotify integration to find music. +https://github.com/estiens/jukebot + +**Sonos Controller (JS / Electron)** + +A Sonos controller, built with the Electron framework. +https://github.com/anton-christensen/sonos-controller + +**Sonos Cron (PHP)** + +Service for retrieving commands from an AWS SQS queue and passing them to an instance of the Sonos HTTP API +https://github.com/cjrpaterson/sonos-cron + +**Sonos Push Server (JS)** + +A Node server to receive notifications from node-sonos-http-api and push them via socket.io to the clients. +https://github.com/TimoKorinth/sonos-push-server + From 493c27ae0c64ba1b22e7a3683b641851040c24bc Mon Sep 17 00:00:00 2001 From: rafale77 Date: Tue, 24 Oct 2017 04:28:50 -0700 Subject: [PATCH 062/123] Update presets-loader.js (#563) Enables running on node 4.xx Strict mode needs to be declared --- lib/presets-loader.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/presets-loader.js b/lib/presets-loader.js index ec296ba3..986cea4a 100644 --- a/lib/presets-loader.js +++ b/lib/presets-loader.js @@ -1,3 +1,4 @@ +"use strict"; const fs = require('fs'); const util = require('util'); const path = require('path'); @@ -84,4 +85,4 @@ try { logger.warn(e.message); } -module.exports = presets; \ No newline at end of file +module.exports = presets; From d5ffca3da60482a057e3736cefffb0cecc5d7e34 Mon Sep 17 00:00:00 2001 From: Jimmy Shimizu Date: Tue, 21 Nov 2017 20:02:29 +0100 Subject: [PATCH 063/123] feat: Add bass/treble actions. resolves #554 --- README.md | 8 +++++++- lib/actions/equalizer.js | 12 ++++++++++++ package.json | 4 ++-- 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 9168ec3a..3d91ab65 100644 --- a/README.md +++ b/README.md @@ -103,6 +103,7 @@ The actions supported as of today: * sub (on/off/gain/crossover/polarity) See SUB section for more info * nightmode (on/off, PLAYBAR only) * speechenhancement (on/off, PLAYBAR only) +* bass/treble (use -10 thru 10 as value. 0 is neutral) State @@ -138,7 +139,12 @@ Example of a state json: "shuffle":true, "repeat":false, "crossfade":false - } + }, + "equalizer": { + "bass": 0, + "treble": 0, + "loudness": true + } } Queue diff --git a/lib/actions/equalizer.js b/lib/actions/equalizer.js index ca1157d7..beba3655 100644 --- a/lib/actions/equalizer.js +++ b/lib/actions/equalizer.js @@ -10,7 +10,19 @@ function speechEnhancement(player, values) { return player.speechEnhancement(enable); } +function bass(player, values) { + const level = parseInt(values[0]); + return player.setBass(level); +} + +function treble(player, values) { + const level = parseInt(values[0]); + return player.setTreble(level); +} + module.exports = function (api) { api.registerAction('nightmode', nightMode); api.registerAction('speechenhancement', speechEnhancement); + api.registerAction('bass', bass); + api.registerAction('treble', treble); } diff --git a/package.json b/package.json index 294a9ef4..98461093 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sonos-http-api", - "version": "1.5.0", + "version": "1.6.0", "description": "A simple node app for controlling a Sonos system with basic HTTP requests", "scripts": { "start": "node server.js", @@ -22,7 +22,7 @@ "music-metadata": "^0.8.4", "node-static": "^0.7.9", "request-promise": "~1.0.2", - "sonos-discovery": "https://github.com/jishi/node-sonos-discovery/archive/v1.5.1.tar.gz", + "sonos-discovery": "https://github.com/jishi/node-sonos-discovery/archive/v1.6.0.tar.gz", "wav-file-info": "0.0.8" }, "engines": { From b021217c67a4cbaca202c3b24bcd3c8c03a6dbce Mon Sep 17 00:00:00 2001 From: MaxRei-dev Date: Fri, 8 Dec 2017 16:24:46 +0100 Subject: [PATCH 064/123] Update tunein.js (#574) Allow to set a tunein Station without starting it to play. --- lib/actions/tunein.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/actions/tunein.js b/lib/actions/tunein.js index a6970ecd..add0fb57 100644 --- a/lib/actions/tunein.js +++ b/lib/actions/tunein.js @@ -23,10 +23,13 @@ function tuneIn(player, values) { return player.coordinator.setAVTransport(uri, metadata) .then(() => player.coordinator.play()); } + if (action == 'set') { + return player.coordinator.setAVTransport(uri, metadata); + } - return Promise.reject('TuneIn only handles the {play} action'); + return Promise.reject('TuneIn only handles the {play} & {set} action'); } module.exports = function (api) { api.registerAction('tunein', tuneIn); -} \ No newline at end of file +} From 5e31916e63b5f96f9255767514b789f6e3e6fe3e Mon Sep 17 00:00:00 2001 From: MaxRei-dev Date: Fri, 8 Dec 2017 16:25:04 +0100 Subject: [PATCH 065/123] Update readme for tunin set (#575) --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 3d91ab65..574ffee6 100644 --- a/README.md +++ b/README.md @@ -830,12 +830,16 @@ Your Pandora credentials need to be added to the settings.json file Tunein ---------------------- -Given a station id this will play the streaming broadcast via the tunein service. You can find tunein station ids via services like [radiotime](http://opml.radiotime.com/) +Given a station id this will play or set the streaming broadcast via the tunein service. You can find tunein station ids via services like [radiotime](http://opml.radiotime.com/) The following endpoint is available: ``` /RoomName/tunein/play/{station id} +Will set and start playing given Station id + +/RoomName/tunein/set/{station id} +Will set without start playing given Station id ``` From aecc8a0a6a3a8e8755a7ea08c7793fe89ccda143 Mon Sep 17 00:00:00 2001 From: Aaron Bieber Date: Sat, 30 Dec 2017 08:14:02 -0500 Subject: [PATCH 066/123] Add ip setting (#582) * Add support for an IP address setting * Provide default IP, update docs --- README.md | 2 ++ server.js | 5 +++-- settings.js | 1 + 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 574ffee6..05f40ce5 100644 --- a/README.md +++ b/README.md @@ -313,6 +313,7 @@ If you want to change default settings, you can create a settings.json file and Available options are: * port: change the listening port +* ip: change the listening IP * https: use https which requires a key and certificate or pfx file * auth: require basic auth credentials which requires a username and password * announceVolume: the percentual volume use when invoking say/sayall without any volume parameter @@ -328,6 +329,7 @@ Example: "name": "ZiraRUS" }, "port": 5005, + "ip": "0.0.0.0", "securePort": 5006, "https": { "key": "/path/to/key.pem", diff --git a/server.js b/server.js index a5700431..b795e13c 100644 --- a/server.js +++ b/server.js @@ -80,8 +80,9 @@ process.on('unhandledRejection', (err) => { logger.error(err); }); -server.listen(settings.port, function () { - logger.info('http server listening on port', settings.port); +let host = settings.ip; +server.listen(settings.port, host, function () { + logger.info('http server listening on', host, 'port', settings.port); }); server.on('error', (err) => { diff --git a/settings.js b/settings.js index 7d8d3ca8..f2531e2d 100644 --- a/settings.js +++ b/settings.js @@ -16,6 +16,7 @@ function merge(target, source) { var settings = { port: 5005, + ip: "0.0.0.0", securePort: 5006, cacheDir: path.resolve(__dirname, 'cache'), webroot: path.resolve(__dirname, 'static'), From 8ec7cfa653becb354b810ca67fe504a9f43efa18 Mon Sep 17 00:00:00 2001 From: "Mark St. John" Date: Sun, 7 Jan 2018 10:37:44 -0600 Subject: [PATCH 067/123] Adding a bunch of missing channels, removing channels that no longer exist and updating channel numbers for various channels that have been updated. --- lib/sirius-channels.json | 236 ++++++++++++++++++++------------------- 1 file changed, 121 insertions(+), 115 deletions(-) diff --git a/lib/sirius-channels.json b/lib/sirius-channels.json index e314d685..27f30ec0 100644 --- a/lib/sirius-channels.json +++ b/lib/sirius-channels.json @@ -1,28 +1,77 @@ [ {"fullTitle":"10 - Pop2K", "channelNum":"10", "title":"Pop2K", "id":"8208", "parentID":"00070044g%3apop"}, + {"fullTitle":"100 - Howard 100", "channelNum":"100", "title":"Howard 100", "id":"howardstern100", "parentID":"00070044g%3ahowardstern"}, + {"fullTitle":"101 - Howard 101", "channelNum":"101", "title":"Howard 101", "id":"howardstern101", "parentID":"00070044g%3ahowardstern"}, + {"fullTitle":"102 - Radio Andy", "channelNum":"102", "title":"Radio Andy", "id":"9409", "parentID":"00070044g%3aentertainment"}, + {"fullTitle":"105 - EW Radio", "channelNum":"105", "title":"EW Radio", "id":"9351", "parentID":"00070044g%3aentertainment"}, + {"fullTitle":"106 - SiriusXM 106", "channelNum":"106", "title":"SiriusXM 106", "id":"siriusoutq", "parentID":"00070044g%3aentertainment"}, + {"fullTitle":"108 - Today Show Radio", "channelNum":"108", "title":"Today Show Radio", "id":"9390", "parentID":"00070044g%3aentertainment"}, + {"fullTitle":"109 - SiriusXM Stars", "channelNum":"109", "title":"SiriusXM Stars", "id":"siriusstars", "parentID":"00070044g%3aentertainment"}, + {"fullTitle":"11 - KIIS-Los Angeles", "channelNum":"11", "title":"KIIS-Los Angeles", "id":"8241", "parentID":"00070044g%3amore"}, + {"fullTitle":"110 - Doctor Radio", "channelNum":"110", "title":"Doctor Radio", "id":"doctorradio", "parentID":"00070044g%3aentertainment"}, + {"fullTitle":"111 - Business Radio", "channelNum":"111", "title":"Business Radio", "id":"9359", "parentID":"00070044g%3aentertainment"}, + {"fullTitle":"112 - CNBC", "channelNum":"112", "title":"CNBC", "id":"cnbc", "parentID":"00070044g%3apublicradio"}, + {"fullTitle":"113 - FOX Business", "channelNum":"113", "title":"FOX Business", "id":"9369", "parentID":"00070044g%3apublicradio"}, + {"fullTitle":"114 - FOX News Channel", "channelNum":"114", "title":"FOX News Channel", "id":"foxnewschannel", "parentID":"00070044g%3apublicradio"}, + {"fullTitle":"115 - FOX News Headlines 24/7", "channelNum":"115", "title":"FOX News Headlines 24/7", "id":"9410", "parentID":"00070044g%3apublicradio"}, + {"fullTitle":"116 - CNN", "channelNum":"116", "title":"CNN", "id":"cnn", "parentID":"00070044g%3apublicradio"}, + {"fullTitle":"117 - HLN", "channelNum":"117", "title":"HLN", "id":"cnnheadlinenews", "parentID":"00070044g%3apublicradio"}, + {"fullTitle":"118 - MSNBC", "channelNum":"118", "title":"MSNBC", "id":"8367", "parentID":"00070044g%3apublicradio"}, + {"fullTitle":"119 - Bloomberg Radio", "channelNum":"119", "title":"Bloomberg Radio", "id":"bloombergradio", "parentID":"00070044g%3apublicradio"}, + {"fullTitle":"12 - Z100/NY", "channelNum":"12", "title":"Z100/NY", "id":"8242", "parentID":"00070044g%3amore"}, + {"fullTitle":"120 - BBC World Service", "channelNum":"120", "title":"BBC World Service", "id":"bbcworld", "parentID":"00070044g%3apublicradio"}, + {"fullTitle":"121 - SiriusXM Insight", "channelNum":"121", "title":"SiriusXM Insight", "id":"8183", "parentID":"00070044g%3apublicradio"}, + {"fullTitle":"122 - NPR Now", "channelNum":"122", "title":"NPR Now", "id":"nprnow", "parentID":"00070044g%3apublicradio"}, + {"fullTitle":"123 - PRX Public Radio", "channelNum":"123", "title":"PRX Public Radio", "id":"8239", "parentID":"00070044g%3apublicradio"}, + {"fullTitle":"124 - POTUS Politics", "channelNum":"124", "title":"POTUS Politics", "id":"indietalk", "parentID":"00070044g%3apolitical"}, + {"fullTitle":"125 - SiriusXM Patriot", "channelNum":"125", "title":"SiriusXM Patriot", "id":"siriuspatriot", "parentID":"00070044g%3apolitical"}, + {"fullTitle":"126 - SiriusXM Urban View", "channelNum":"126", "title":"SiriusXM Urban View", "id":"8238", "parentID":"00070044g%3apolitical"}, + {"fullTitle":"127 - SiriusXM Progress", "channelNum":"127", "title":"SiriusXM Progress", "id":"siriusleft", "parentID":"00070044g%3apolitical"}, + {"fullTitle":"128 - Joel Osteen Radio", "channelNum":"128", "title":"Joel Osteen Radio", "id":"9392", "parentID":"00070044g%3aentertainment"}, + {"fullTitle":"129 - The Catholic Channel", "channelNum":"129", "title":"The Catholic Channel", "id":"thecatholicchannel", "parentID":"00070044g%3areligion"}, {"fullTitle":"13 - Velvet", "channelNum":"13", "title":"Velvet", "id":"9361", "parentID":"00070044g%3apop"}, + {"fullTitle":"130 - EWTN Radio", "channelNum":"130", "title":"EWTN Radio", "id":"ewtnglobal", "parentID":"00070044g%3areligion"}, + {"fullTitle":"131 - Family Talk", "channelNum":"131", "title":"Family Talk", "id":"8307", "parentID":"00070044g%3areligion"}, {"fullTitle":"14 - The Coffee House", "channelNum":"14", "title":"The Coffee House", "id":"coffeehouse", "parentID":"00070044g%3apop"}, + {"fullTitle":"141 - HUR Voices", "channelNum":"141", "title":"HUR Voices", "id":"9129", "parentID":"00070044g%3amore"}, + {"fullTitle":"142 - HBCU", "channelNum":"142", "title":"HBCU", "id":"9130", "parentID":"00070044g%3amore"}, + {"fullTitle":"143 - BYUradio", "channelNum":"143", "title":"BYUradio", "id":"9131", "parentID":"00070044g%3amore"}, + {"fullTitle":"144 - Korea Today", "channelNum":"144", "title":"Korea Today", "id":"9132", "parentID":"00070044g%3amore"}, + {"fullTitle":"146 - Road Dog Trucking", "channelNum":"146", "title":"Road Dog Trucking", "id":"roaddogtrucking", "parentID":"00070044g%3aentertainment"}, + {"fullTitle":"147 - RURAL Radio", "channelNum":"147", "title":"RURAL Radio", "id":"9367", "parentID":"00070044g%3apublicradio"}, + {"fullTitle":"148 - RadioClassics", "channelNum":"148", "title":"RadioClassics", "id":"radioclassics", "parentID":"00070044g%3aentertainment"}, {"fullTitle":"15 - The Pulse", "channelNum":"15", "title":"The Pulse", "id":"thepulse", "parentID":"00070044g%3apop"}, + {"fullTitle":"152 - En Vivo", "channelNum":"152", "title":"En Vivo", "id":"9135", "parentID":"00070044g%3amore"}, + {"fullTitle":"153 - Cristina Radio", "channelNum":"153", "title":"Cristina Radio", "id":"9134", "parentID":"00070044g%3amore"}, + {"fullTitle":"154 - American Latino Radio", "channelNum":"154", "title":"American Latino Radio", "id":"9133", "parentID":"00070044g%3amore"}, + {"fullTitle":"155 - CNN en Español", "channelNum":"155", "title":"CNN en Español", "id":"cnnespanol", "parentID":"00070044g%3apublicradio"}, + {"fullTitle":"157 - ESPN Deportes", "channelNum":"157", "title":"ESPN Deportes", "id":"espndeportes", "parentID":"00070044g%3asportstalk"}, {"fullTitle":"158 - Caliente", "channelNum":"158", "title":"Caliente", "id":"rumbon", "parentID":"00070044g%3apop"}, {"fullTitle":"16 - The Blend", "channelNum":"16", "title":"The Blend", "id":"starlite", "parentID":"00070044g%3apop"}, + {"fullTitle":"162 - CBC Radio 3", "channelNum":"162", "title":"CBC Radio 3", "id":"cbcradio3", "parentID":"00070044g%3acanadian"}, + {"fullTitle":"163 - Ici Musique Chansons", "channelNum":"163", "title":"Ici Musique Chansons", "id":"8245", "parentID":"00070044g%3acanadian"}, + {"fullTitle":"165 - Multicultural Radio", "channelNum":"165", "title":"Multicultural Radio", "id":"9358", "parentID":"00070044g%3acanadian"}, + {"fullTitle":"166 - Ici FrancoCountry", "channelNum":"166", "title":"Ici FrancoCountry", "id":"rockvelours", "parentID":"00070044g%3acanadian"}, + {"fullTitle":"167 - Canada Talks", "channelNum":"167", "title":"Canada Talks", "id":"9172", "parentID":"00070044g%3acanadian"}, + {"fullTitle":"168 - Canada Laughs", "channelNum":"168", "title":"Canada Laughs", "id":"8259", "parentID":"00070044g%3acomedy"}, + {"fullTitle":"169 - CBC Radio One", "channelNum":"169", "title":"CBC Radio One", "id":"cbcradioone", "parentID":"00070044g%3apublicradio"}, {"fullTitle":"17 - SiriusXM Love", "channelNum":"17", "title":"SiriusXM Love", "id":"siriuslove", "parentID":"00070044g%3apop"}, + {"fullTitle":"170 - Ici Première", "channelNum":"170", "title":"Ici Première", "id":"premiereplus", "parentID":"00070044g%3acanadian"}, + {"fullTitle":"171 - CBC Country", "channelNum":"171", "title":"CBC Country", "id":"bandeapart", "parentID":"00070044g%3acanadian"}, + {"fullTitle":"172 - Canada 360 by AMI", "channelNum":"172", "title":"Canada 360 by AMI", "id":"8248", "parentID":"00070044g%3acanadian"}, + {"fullTitle":"173 - The Verge", "channelNum":"173", "title":"The Verge", "id":"8244", "parentID":"00070044g%3acanadian"}, + {"fullTitle":"174 - Influence Franco", "channelNum":"174", "title":"Influence Franco", "id":"8246", "parentID":"00070044g%3acanadian"}, {"fullTitle":"18 - SXM Limited Edition", "channelNum":"18", "title":"SXM Limited Edition", "id":"9138", "parentID":"00070044g%3apop"}, - {"fullTitle":"2 - SiriusXM Hits 1", "channelNum":"2", "title":"SiriusXM Hits 1", "id":"siriushits1", "parentID":"00070044g%3apop"}, - {"fullTitle":"3 - Venus", "channelNum":"3", "title":"Venus", "id":"9389", "parentID":"00070044g%3apop"}, - {"fullTitle":"300 - Poptropolis", "channelNum":"300", "title":"Poptropolis", "id":"9412", "parentID":"00070044g%3apop"}, - {"fullTitle":"301 - Road Trip Radio", "channelNum":"301", "title":"Road Trip Radio", "id":"9415", "parentID":"00070044g%3apop"}, - {"fullTitle":"302 - The Covers Channel", "channelNum":"302", "title":"The Covers Channel", "id":"9416", "parentID":"00070044g%3apop"}, - {"fullTitle":"4 - Pitbulls Globalization", "channelNum":"4", "title":"Pitbulls Globalization", "id":"9406", "parentID":"00070044g%3apop"}, - {"fullTitle":"5 - 50s on 5", "channelNum":"5", "title":"50s on 5", "id":"siriusgold", "parentID":"00070044g%3apop"}, - {"fullTitle":"6 - 60s on 6", "channelNum":"6", "title":"60s on 6", "id":"60svibrations", "parentID":"00070044g%3apop"}, - {"fullTitle":"7 - 70s on 7", "channelNum":"7", "title":"70s on 7", "id":"totally70s", "parentID":"00070044g%3apop"}, - {"fullTitle":"700 - Neil Diamond Radio", "channelNum":"700", "title":"Neil Diamond Radio", "id":"8372", "parentID":"00070044g%3apop"}, - {"fullTitle":"703 - Elevations", "channelNum":"703", "title":"Elevations", "id":"9362", "parentID":"00070044g%3apop"}, - {"fullTitle":"8 - 80s on 8", "channelNum":"8", "title":"80s on 8", "id":"big80s", "parentID":"00070044g%3apop"}, - {"fullTitle":"9 - 90s on 9", "channelNum":"9", "title":"90s on 9", "id":"8206", "parentID":"00070044g%3apop"}, {"fullTitle":"19 - Elvis Radio", "channelNum":"19", "title":"Elvis Radio", "id":"elvisradio", "parentID":"00070044g%3arock"}, + {"fullTitle":"2 - SiriusXM Hits 1", "channelNum":"2", "title":"SiriusXM Hits 1", "id":"siriushits1", "parentID":"00070044g%3apop"}, {"fullTitle":"20 - E Street Radio", "channelNum":"20", "title":"E Street Radio", "id":"estreetradio", "parentID":"00070044g%3arock"}, + {"fullTitle":"205 - NBC Sports Radio", "channelNum":"205", "title":"NBC Sports Radio", "id":"9452", "parentID":"00070044g%3asportstalk"}, + {"fullTitle":"206 - Opie Radio", "channelNum":"206", "title":"Opie Radio", "id":"8184", "parentID":"00070044g%3aentertainment"}, + {"fullTitle":"207 - SiriusXM NBA Radio", "channelNum":"207", "title":"SiriusXM NBA Radio", "id":"9385", "parentID":"00070044g%3asportstalk"}, + {"fullTitle":"208 - SiriusXM PGA TOUR Radio", "channelNum":"208", "title":"SiriusXM PGA TOUR Radio", "id":"8186", "parentID":"00070044g%3asportstalk"}, + {"fullTitle":"209 - MLB Network Radio", "channelNum":"209", "title":"MLB Network Radio", "id":"8333", "parentID":"00070044g%3asportstalk"}, {"fullTitle":"21 - Underground Garage", "channelNum":"21", "title":"Underground Garage", "id":"undergroundgarage", "parentID":"00070044g%3arock"}, + {"fullTitle":"210 - SXM Fantasy Sports Radio", "channelNum":"210", "title":"SXM Fantasy Sports Radio", "id":"8368", "parentID":"00070044g%3asportstalk"}, {"fullTitle":"22 - Pearl Jam Radio", "channelNum":"22", "title":"Pearl Jam Radio", "id":"8370", "parentID":"00070044g%3arock"}, {"fullTitle":"23 - Grateful Dead", "channelNum":"23", "title":"Grateful Dead", "id":"gratefuldead", "parentID":"00070044g%3arock"}, {"fullTitle":"24 - Radio Margaritaville", "channelNum":"24", "title":"Radio Margaritaville", "id":"radiomargaritaville", "parentID":"00070044g%3arock"}, @@ -31,35 +80,48 @@ {"fullTitle":"27 - Deep Tracks", "channelNum":"27", "title":"Deep Tracks", "id":"thevault", "parentID":"00070044g%3arock"}, {"fullTitle":"28 - The Spectrum", "channelNum":"28", "title":"The Spectrum", "id":"thespectrum", "parentID":"00070044g%3arock"}, {"fullTitle":"29 - Jam_ON", "channelNum":"29", "title":"Jam_ON", "id":"jamon", "parentID":"00070044g%3arock"}, + {"fullTitle":"3 - Venus", "channelNum":"3", "title":"Venus", "id":"9389", "parentID":"00070044g%3apop"}, {"fullTitle":"30 - The Loft", "channelNum":"30", "title":"The Loft", "id":"8207", "parentID":"00070044g%3arock"}, + {"fullTitle":"300 - Poptropolis", "channelNum":"300", "title":"Poptropolis", "id":"9412", "parentID":"00070044g%3apop"}, + {"fullTitle":"301 - Road Trip Radio", "channelNum":"301", "title":"Road Trip Radio", "id":"9415", "parentID":"00070044g%3apop"}, + {"fullTitle":"302 - The Covers Channel", "channelNum":"302", "title":"The Covers Channel", "id":"9416", "parentID":"00070044g%3apop"}, {"fullTitle":"31 - Tom Petty Radio", "channelNum":"31", "title":"Tom Petty Radio", "id":"9407", "parentID":"00070044g%3arock"}, + {"fullTitle":"310 - Rock and Roll Hall of Fame Radio", "channelNum":"310", "title":"Rock and Roll Hall of Fame Radio", "id":"9174", "parentID":"00070044g%3arock"}, {"fullTitle":"310 - SXM Rock Hall Radio", "channelNum":"310", "title":"SXM Rock Hall Radio", "id":"9174", "parentID":"00070044g%3arock"}, + {"fullTitle":"311 - Yacht Rock Radio", "channelNum":"311", "title":"Yacht Rock Radio", "id":"9420", "parentID":"00070044g%3arock"}, {"fullTitle":"312 - Pettys Buried Treasure", "channelNum":"312", "title":"Pettys Buried Treasure", "id":"9352", "parentID":"00070044g%3arock"}, {"fullTitle":"313 - RockBar", "channelNum":"313", "title":"RockBar", "id":"9175", "parentID":"00070044g%3arock"}, - {"fullTitle":"314 - SiriusXM Turbo", "channelNum":"314", "title":"SiriusXM Turbo", "id":"9413", "parentID":"00070044g%3arock"}, + {"fullTitle":"314 - Faction Punk", "channelNum":"314", "title":"Faction Punk", "id":"faction", "parentID":"00070044g%3arock"}, {"fullTitle":"316 - SiriusXM Comes Alive!", "channelNum":"316", "title":"SiriusXM Comes Alive!", "id":"9176", "parentID":"00070044g%3arock"}, {"fullTitle":"32 - The Bridge", "channelNum":"32", "title":"The Bridge", "id":"thebridge", "parentID":"00070044g%3arock"}, {"fullTitle":"33 - 1st Wave", "channelNum":"33", "title":"1st Wave", "id":"firstwave", "parentID":"00070044g%3arock"}, + {"fullTitle":"330 - SiriusXM Silk", "channelNum":"330", "title":"SiriusXM Silk", "id":"9364", "parentID":"00070044g%3arandb"}, {"fullTitle":"34 - Lithium", "channelNum":"34", "title":"Lithium", "id":"90salternative", "parentID":"00070044g%3arock"}, + {"fullTitle":"340 - Tiëstos Club Life Radio", "channelNum":"340", "title":"Tiëstos Club Life Radio", "id":"9219", "parentID":"00070044g%3adance"}, {"fullTitle":"35 - SiriusXMU", "channelNum":"35", "title":"SiriusXMU", "id":"leftofcenter", "parentID":"00070044g%3arock"}, + {"fullTitle":"350 - Red White & Booze", "channelNum":"350", "title":"Red White & Booze", "id":"9178", "parentID":"00070044g%3acountry"}, {"fullTitle":"36 - Alt Nation", "channelNum":"36", "title":"Alt Nation", "id":"altnation", "parentID":"00070044g%3arock"}, {"fullTitle":"37 - Octane", "channelNum":"37", "title":"Octane", "id":"octane", "parentID":"00070044g%3arock"}, + {"fullTitle":"370 - SportsCenter", "channelNum":"370", "title":"SportsCenter", "id":"9180", "parentID":"00070044g%3asportstalk"}, {"fullTitle":"38 - Ozzys Boneyard", "channelNum":"38", "title":"Ozzys Boneyard", "id":"buzzsaw", "parentID":"00070044g%3arock"}, {"fullTitle":"39 - Hair Nation", "channelNum":"39", "title":"Hair Nation", "id":"hairnation", "parentID":"00070044g%3arock"}, + {"fullTitle":"4 - Pitbulls Globalization", "channelNum":"4", "title":"Pitbulls Globalization", "id":"9406", "parentID":"00070044g%3apop"}, {"fullTitle":"40 - Liquid Metal", "channelNum":"40", "title":"Liquid Metal", "id":"hardattack", "parentID":"00070044g%3arock"}, - {"fullTitle":"41 - Faction", "channelNum":"41", "title":"Faction", "id":"faction", "parentID":"00070044g%3arock"}, + {"fullTitle":"400 - Carlins Corner", "channelNum":"400", "title":"Carlins Corner", "id":"9181", "parentID":"00070044g%3acomedy"}, + {"fullTitle":"41 - SiriusXM Turbo", "channelNum":"41", "title":"SiriusXM Turbo", "id":"9413", "parentID":"00070044g%3arock"}, + {"fullTitle":"415 - Vivid Radio", "channelNum":"415", "title":"Vivid Radio", "id":"8369", "parentID":"00070044g%3aentertainment"}, {"fullTitle":"42 - The Joint", "channelNum":"42", "title":"The Joint", "id":"reggaerhythms", "parentID":"00070044g%3arock"}, - {"fullTitle":"713 - Jason Ellis", "channelNum":"713", "title":"Jason Ellis", "id":"9363", "parentID":"00070044g%3arock"}, - {"fullTitle":"330 - SiriusXM Silk", "channelNum":"330", "title":"SiriusXM Silk", "id":"9364", "parentID":"00070044g%3arandb"}, - {"fullTitle":"340 - Tiëstos Club Life Radio", "channelNum":"340", "title":"Tiëstos Club Life Radio", "id":"9219", "parentID":"00070044g%3adance"}, - {"fullTitle":"350 - Red White & Booze", "channelNum":"350", "title":"Red White & Booze", "id":"9178", "parentID":"00070044g%3acountry"}, {"fullTitle":"43 - Backspin", "channelNum":"43", "title":"Backspin", "id":"8124", "parentID":"00070044g%3ahiphop"}, {"fullTitle":"44 - Hip-Hop Nation", "channelNum":"44", "title":"Hip-Hop Nation", "id":"hiphopnation", "parentID":"00070044g%3ahiphop"}, {"fullTitle":"45 - Shade 45", "channelNum":"45", "title":"Shade 45", "id":"shade45", "parentID":"00070044g%3ahiphop"}, + {"fullTitle":"450 - FOX News Talk", "channelNum":"450", "title":"FOX News Talk", "id":"9370", "parentID":"00070044g%3apolitical"}, + {"fullTitle":"455 - C-SPAN Radio", "channelNum":"455", "title":"C-SPAN Radio", "id":"8237", "parentID":"00070044g%3apublicradio"}, {"fullTitle":"46 - The Heat", "channelNum":"46", "title":"The Heat", "id":"hotjamz", "parentID":"00070044g%3arandb"}, {"fullTitle":"47 - SiriusXM FLY", "channelNum":"47", "title":"SiriusXM FLY", "id":"9339", "parentID":"00070044g%3arandb"}, + {"fullTitle":"470 - El Paisa", "channelNum":"470", "title":"El Paisa", "id":"9414", "parentID":"00070044g%3amore"}, {"fullTitle":"48 - Heart & Soul", "channelNum":"48", "title":"Heart & Soul", "id":"heartandsoul", "parentID":"00070044g%3arandb"}, {"fullTitle":"49 - Soul Town", "channelNum":"49", "title":"Soul Town", "id":"soultown", "parentID":"00070044g%3arandb"}, + {"fullTitle":"5 - 50s on 5", "channelNum":"5", "title":"50s on 5", "id":"siriusgold", "parentID":"00070044g%3apop"}, {"fullTitle":"50 - The Groove", "channelNum":"50", "title":"The Groove", "id":"8228", "parentID":"00070044g%3arandb"}, {"fullTitle":"51 - BPM", "channelNum":"51", "title":"BPM", "id":"thebeat", "parentID":"00070044g%3adance"}, {"fullTitle":"52 - Electric Area", "channelNum":"52", "title":"Electric Area", "id":"area33", "parentID":"00070044g%3adance"}, @@ -70,16 +132,10 @@ {"fullTitle":"57 - Y2Kountry", "channelNum":"57", "title":"Y2Kountry", "id":"9340", "parentID":"00070044g%3acountry"}, {"fullTitle":"58 - Prime Country", "channelNum":"58", "title":"Prime Country", "id":"primecountry", "parentID":"00070044g%3acountry"}, {"fullTitle":"59 - Willies Roadhouse", "channelNum":"59", "title":"Willies Roadhouse", "id":"theroadhouse", "parentID":"00070044g%3acountry"}, + {"fullTitle":"6 - 60s on 6", "channelNum":"6", "title":"60s on 6", "id":"60svibrations", "parentID":"00070044g%3apop"}, {"fullTitle":"60 - Outlaw Country", "channelNum":"60", "title":"Outlaw Country", "id":"outlawcountry", "parentID":"00070044g%3acountry"}, {"fullTitle":"61 - Bluegrass Junction", "channelNum":"61", "title":"Bluegrass Junction", "id":"bluegrass", "parentID":"00070044g%3acountry"}, {"fullTitle":"62 - No Shoes Radio", "channelNum":"62", "title":"No Shoes Radio", "id":"9418", "parentID":"00070044g%3acountry"}, - {"fullTitle":"715 - SXM Limited Edition 2", "channelNum":"715", "title":"SXM Limited Edition 2", "id":"9139", "parentID":"00070044g%3arock"}, - {"fullTitle":"716 - SXM Limited Edition 3", "channelNum":"716", "title":"SXM Limited Edition 3", "id":"9353", "parentID":"00070044g%3arock"}, - {"fullTitle":"720 - Sways Universe", "channelNum":"720", "title":"Sways Universe", "id":"9397", "parentID":"00070044g%3ahiphop"}, - {"fullTitle":"721 - SXM Limited Edition 4", "channelNum":"721", "title":"SXM Limited Edition 4", "id":"9398", "parentID":"00070044g%3ahiphop"}, - {"fullTitle":"726 - SXM Limited Edition 5", "channelNum":"726", "title":"SXM Limited Edition 5", "id":"9399", "parentID":"00070044g%3arandb"}, - {"fullTitle":"730 - SXM Limited Edition 6", "channelNum":"730", "title":"SXM Limited Edition 6", "id":"9400", "parentID":"00070044g%3adance"}, - {"fullTitle":"741 - The Village", "channelNum":"741", "title":"The Village", "id":"8227", "parentID":"00070044g%3acountry"}, {"fullTitle":"63 - The Message", "channelNum":"63", "title":"The Message", "id":"spirit", "parentID":"00070044g%3achristian"}, {"fullTitle":"64 - Kirk Franklins Praise", "channelNum":"64", "title":"Kirk Franklins Praise", "id":"praise", "parentID":"00070044g%3achristian"}, {"fullTitle":"65 - enLighten", "channelNum":"65", "title":"enLighten", "id":"8229", "parentID":"00070044g%3achristian"}, @@ -87,11 +143,28 @@ {"fullTitle":"67 - Real Jazz", "channelNum":"67", "title":"Real Jazz", "id":"purejazz", "parentID":"00070044g%3ajazz"}, {"fullTitle":"68 - Spa", "channelNum":"68", "title":"Spa", "id":"spa73", "parentID":"00070044g%3ajazz"}, {"fullTitle":"69 - Escape", "channelNum":"69", "title":"Escape", "id":"8215", "parentID":"00070044g%3ajazz"}, + {"fullTitle":"7 - 70s on 7", "channelNum":"7", "title":"70s on 7", "id":"totally70s", "parentID":"00070044g%3apop"}, {"fullTitle":"70 - BB Kings Bluesville", "channelNum":"70", "title":"BB Kings Bluesville", "id":"siriusblues", "parentID":"00070044g%3ajazz"}, + {"fullTitle":"700 - Neil Diamond Radio", "channelNum":"700", "title":"Neil Diamond Radio", "id":"8372", "parentID":"00070044g%3apop"}, + {"fullTitle":"702 - Elevations", "channelNum":"702", "title":"Elevations", "id":"9362", "parentID":"00070044g%3apop"}, + {"fullTitle":"703 - Oldies Party", "channelNum":"703", "title":"Oldies Party", "id":"9378", "parentID":"00070044g%3aparty"}, + {"fullTitle":"704 - 70s/80s Pop", "channelNum":"770", "title":"70s/80s Pop", "id":"9372", "parentID":"00070044g%3aparty"}, + {"fullTitle":"705 - 80s/90s Pop", "channelNum":"771", "title":"80s/90s Pop", "id":"9373", "parentID":"00070044g%3aparty"}, {"fullTitle":"71 - Siriusly Sinatra", "channelNum":"71", "title":"Siriusly Sinatra", "id":"siriuslysinatra", "parentID":"00070044g%3ajazz"}, + {"fullTitle":"712 - Tom Pettys Buried Treasure", "channelNum":"712", "title":"Tom Pettys Buried Treasure", "id":"9352", "parentID":"00070044g%3arock"}, + {"fullTitle":"713 - The Emo Project", "channelNum":"713", "title":"The Emo Project", "id":"9447", "parentID":"00070044g%3arock"}, + {"fullTitle":"714 - Indie 1.0", "channelNum":"714", "title":"Indie 1.0", "id":"9451", "parentID":"00070044g%3arock"}, + {"fullTitle":"715 - Classic Rock Party", "channelNum":"715", "title":"Classic Rock Party", "id":"9375", "parentID":"00070044g%3aparty"}, + {"fullTitle":"716 - SXM Limited Edition 2", "channelNum":"716", "title":"SXM Limited Edition 2", "id":"9139", "parentID":"00070044g%3arock"}, + {"fullTitle":"717 - SXM Limited Edition 3", "channelNum":"717", "title":"SXM Limited Edition 3", "id":"9353", "parentID":"00070044g%3arock"}, {"fullTitle":"72 - On Broadway", "channelNum":"72", "title":"On Broadway", "id":"broadwaysbest", "parentID":"00070044g%3ajazz"}, + {"fullTitle":"720 - Sways Universe", "channelNum":"720", "title":"Sways Universe", "id":"9397", "parentID":"00070044g%3ahiphop"}, + {"fullTitle":"721 - SXM Limited Edition 4", "channelNum":"721", "title":"SXM Limited Edition 4", "id":"9398", "parentID":"00070044g%3ahiphop"}, + {"fullTitle":"726 - SXM Limited Edition 5", "channelNum":"726", "title":"SXM Limited Edition 5", "id":"9399", "parentID":"00070044g%3arandb"}, {"fullTitle":"73 - 40s Junction", "channelNum":"73", "title":"40s Junction", "id":"8205", "parentID":"00070044g%3ajazz"}, + {"fullTitle":"730 - SXM Limited Edition 6", "channelNum":"730", "title":"SXM Limited Edition 6", "id":"9400", "parentID":"00070044g%3adance"}, {"fullTitle":"74 - Met Opera Radio", "channelNum":"74", "title":"Met Opera Radio", "id":"metropolitanopera", "parentID":"00070044g%3aclassical"}, + {"fullTitle":"741 - The Village", "channelNum":"741", "title":"The Village", "id":"8227", "parentID":"00070044g%3acountry"}, {"fullTitle":"742 - SXM Limited Edition 7", "channelNum":"742", "title":"SXM Limited Edition 7", "id":"9401", "parentID":"00070044g%3acountry"}, {"fullTitle":"745 - SXM Limited Edition 8", "channelNum":"745", "title":"SXM Limited Edition 8", "id":"9402", "parentID":"00070044g%3achristian"}, {"fullTitle":"750 - Cinemagic", "channelNum":"750", "title":"Cinemagic", "id":"8211", "parentID":"00070044g%3ajazz"}, @@ -99,6 +172,8 @@ {"fullTitle":"752 - SXM Limited Edition 9", "channelNum":"752", "title":"SXM Limited Edition 9", "id":"9403", "parentID":"00070044g%3ajazz"}, {"fullTitle":"755 - SiriusXM Pops", "channelNum":"755", "title":"SiriusXM Pops", "id":"siriuspops", "parentID":"00070044g%3aclassical"}, {"fullTitle":"756 - SXM Limited Edition 10", "channelNum":"756", "title":"SXM Limited Edition 10", "id":"9404", "parentID":"00070044g%3aclassical"}, + {"fullTitle":"758 - Iceberg", "channelNum":"758", "title":"Iceberg", "id":"icebergradio", "parentID":"00070044g%3acanadian"}, + {"fullTitle":"759 - Attitude Franco", "channelNum":"759", "title":"Attitude Franco", "id":"energie2", "parentID":"00070044g%3acanadian"}, {"fullTitle":"76 - Symphony Hall", "channelNum":"76", "title":"Symphony Hall", "id":"symphonyhall", "parentID":"00070044g%3aclassical"}, {"fullTitle":"761 - Águila", "channelNum":"761", "title":"Águila", "id":"9186", "parentID":"00070044g%3aworld"}, {"fullTitle":"762 - Caricia", "channelNum":"762", "title":"Caricia", "id":"9188", "parentID":"00070044g%3aworld"}, @@ -108,24 +183,25 @@ {"fullTitle":"766 - Luna", "channelNum":"766", "title":"Luna", "id":"9189", "parentID":"00070044g%3aworld"}, {"fullTitle":"767 - Rumbón", "channelNum":"767", "title":"Rumbón", "id":"9190", "parentID":"00070044g%3aworld"}, {"fullTitle":"768 - La Kueva", "channelNum":"768", "title":"La Kueva", "id":"9191", "parentID":"00070044g%3aworld"}, - {"fullTitle":"157 - ESPN Deportes", "channelNum":"157", "title":"ESPN Deportes", "id":"espndeportes", "parentID":"00070044g%3asportstalk"}, - {"fullTitle":"207 - SiriusXM NBA Radio", "channelNum":"207", "title":"SiriusXM NBA Radio", "id":"9385", "parentID":"00070044g%3asportstalk"}, - {"fullTitle":"208 - SiriusXM PGA TOUR Radio", "channelNum":"208", "title":"SiriusXM PGA TOUR Radio", "id":"8186", "parentID":"00070044g%3asportstalk"}, - {"fullTitle":"209 - MLB Network Radio", "channelNum":"209", "title":"MLB Network Radio", "id":"8333", "parentID":"00070044g%3asportstalk"}, - {"fullTitle":"210 - SXM Fantasy Sports Radio", "channelNum":"210", "title":"SXM Fantasy Sports Radio", "id":"8368", "parentID":"00070044g%3asportstalk"}, - {"fullTitle":"370 - SportsCenter", "channelNum":"370", "title":"SportsCenter", "id":"9180", "parentID":"00070044g%3asportstalk"}, - {"fullTitle":"770 - 70s/80s Pop", "channelNum":"770", "title":"70s/80s Pop", "id":"9372", "parentID":"00070044g%3aparty"}, - {"fullTitle":"771 - 80s/90s Pop", "channelNum":"771", "title":"80s/90s Pop", "id":"9373", "parentID":"00070044g%3aparty"}, - {"fullTitle":"772 - 90s/2K Pop", "channelNum":"772", "title":"90s/2K Pop", "id":"9374", "parentID":"00070044g%3aparty"}, - {"fullTitle":"773 - Classic Rock Party", "channelNum":"773", "title":"Classic Rock Party", "id":"9375", "parentID":"00070044g%3aparty"}, - {"fullTitle":"774 - Rockin Frat Party", "channelNum":"774", "title":"Rockin Frat Party", "id":"9376", "parentID":"00070044g%3aparty"}, - {"fullTitle":"775 - Hip-Hop Party", "channelNum":"775", "title":"Hip-Hop Party", "id":"9377", "parentID":"00070044g%3aparty"}, - {"fullTitle":"776 - Oldies Party", "channelNum":"776", "title":"Oldies Party", "id":"9378", "parentID":"00070044g%3aparty"}, - {"fullTitle":"777 - Pop Party Mix", "channelNum":"777", "title":"Pop Party Mix", "id":"9379", "parentID":"00070044g%3aparty"}, - {"fullTitle":"778 - Punk Party", "channelNum":"778", "title":"Punk Party", "id":"9380", "parentID":"00070044g%3aparty"}, - {"fullTitle":"779 - New Wave Dance Party", "channelNum":"779", "title":"New Wave Dance Party", "id":"9381", "parentID":"00070044g%3aparty"}, + {"fullTitle":"77 - KIDZ BOP Radio", "channelNum":"77", "title":"KIDZ BOP Radio", "id":"9366", "parentID":"00070044g%3akids"}, + {"fullTitle":"78 - Kids Place Live", "channelNum":"78", "title":"Kids Place Live", "id":"8216", "parentID":"00070044g%3akids"}, {"fullTitle":"780 - The Girls Room", "channelNum":"780", "title":"The Girls Room", "id":"9382", "parentID":"00070044g%3aparty"}, {"fullTitle":"781 - Holly", "channelNum":"781", "title":"Holly", "id":"9343", "parentID":"00070044g%3aparty"}, + {"fullTitle":"782 - Holiday Traditions", "channelNum":"782", "title":"Holiday Traditions", "id":"9342", "parentID":"00070044g%3aparty"}, + {"fullTitle":"783 - Holiday Pops", "channelNum":"783", "title":"Holiday Pops", "id":"9344", "parentID":"00070044g%3aparty"}, + {"fullTitle":"784 - Country Christmas", "channelNum":"784", "title":"Country Christmas", "id":"9345", "parentID":"00070044g%3aparty"}, + {"fullTitle":"785 - Navidad", "channelNum":"785", "title":"Navidad", "id":"9348", "parentID":"00070044g%3aparty"}, + {"fullTitle":"786 - Holiday Soul", "channelNum":"786", "title":"Holiday Soul", "id":"9346", "parentID":"00070044g%3aparty"}, + {"fullTitle":"787 - Radio Hanukkah", "channelNum":"787", "title":"Radio Hanukkah", "id":"9349", "parentID":"00070044g%3aparty"}, + {"fullTitle":"79 - Radio Disney", "channelNum":"79", "title":"Radio Disney", "id":"radiodisney", "parentID":"00070044g%3akids"}, + {"fullTitle":"790 - SXM Limited Edition 11", "channelNum":"790", "title":"SXM Limited Edition 11", "id":"9405", "parentID":"00070044g%3aentertainment"}, + {"fullTitle":"791 - Jason Ellis", "channelNum":"791", "title":"Jason Ellis", "id":"9363", "parentID":"00070044g%3arock"}, + {"fullTitle":"794 - SiriusXM Preview", "channelNum":"794", "title":"SiriusXM Preview", "id":"0000", "parentID":"00070044g%3aentertainment"}, + {"fullTitle":"795 - France 24", "channelNum":"795", "title":"France 24", "id":"9417", "parentID":"00070044g%3apublicradio"}, + {"fullTitle":"796 - TheBlaze Radio Network", "channelNum":"796", "title":"TheBlaze Radio Network", "id":"9355", "parentID":"00070044g%3apolitical"}, + {"fullTitle":"797 - SiriusXM Patriot Plus", "channelNum":"797", "title":"SiriusXM Patriot Plus", "id":"8235", "parentID":"00070044g%3apolitical"}, + {"fullTitle":"798 - SiriusXM Progress Plus", "channelNum":"798", "title":"SiriusXM Progress Plus", "id":"9137", "parentID":"00070044g%3apolitical"}, + {"fullTitle":"8 - 80s on 8", "channelNum":"8", "title":"80s on 8", "id":"big80s", "parentID":"00070044g%3apop"}, {"fullTitle":"80 - ESPN Radio", "channelNum":"80", "title":"ESPN Radio", "id":"espnradio", "parentID":"00070044g%3asportstalk"}, {"fullTitle":"81 - ESPN Xtra", "channelNum":"81", "title":"ESPN Xtra", "id":"8254", "parentID":"00070044g%3asportstalk"}, {"fullTitle":"82 - Mad Dog Sports Radio", "channelNum":"82", "title":"Mad Dog Sports Radio", "id":"8213", "parentID":"00070044g%3asportstalk"}, @@ -133,84 +209,14 @@ {"fullTitle":"84 - College Sports Nation", "channelNum":"84", "title":"College Sports Nation", "id":"siriussportsaction", "parentID":"00070044g%3asportstalk"}, {"fullTitle":"85 - SiriusXM FC", "channelNum":"85", "title":"SiriusXM FC", "id":"9341", "parentID":"00070044g%3asportstalk"}, {"fullTitle":"88 - SiriusXM NFL Radio", "channelNum":"88", "title":"SiriusXM NFL Radio", "id":"siriusnflradio", "parentID":"00070044g%3asportstalk"}, + {"fullTitle":"9 - 90s on 9", "channelNum":"9", "title":"90s on 9", "id":"8206", "parentID":"00070044g%3apop"}, {"fullTitle":"90 - SiriusXM NASCAR Radio", "channelNum":"90", "title":"SiriusXM NASCAR Radio", "id":"siriusnascarradio", "parentID":"00070044g%3asportstalk"}, {"fullTitle":"91 - SXM NHL Network Radio", "channelNum":"91", "title":"SXM NHL Network Radio", "id":"8185", "parentID":"00070044g%3asportstalk"}, {"fullTitle":"93 - SiriusXM Rush", "channelNum":"93", "title":"SiriusXM Rush", "id":"8230", "parentID":"00070044g%3asportstalk"}, - {"fullTitle":"100 - Howard 100", "channelNum":"100", "title":"Howard 100", "id":"howardstern100", "parentID":"00070044g%3ahowardstern"}, - {"fullTitle":"101 - Howard 101", "channelNum":"101", "title":"Howard 101", "id":"howardstern101", "parentID":"00070044g%3ahowardstern"}, - {"fullTitle":"112 - CNBC", "channelNum":"112", "title":"CNBC", "id":"cnbc", "parentID":"00070044g%3apublicradio"}, - {"fullTitle":"113 - FOX Business", "channelNum":"113", "title":"FOX Business", "id":"9369", "parentID":"00070044g%3apublicradio"}, - {"fullTitle":"114 - FOX News Channel", "channelNum":"114", "title":"FOX News Channel", "id":"foxnewschannel", "parentID":"00070044g%3apublicradio"}, - {"fullTitle":"115 - FOX News Headlines 24/7", "channelNum":"115", "title":"FOX News Headlines 24/7", "id":"9410", "parentID":"00070044g%3apublicradio"}, - {"fullTitle":"116 - CNN", "channelNum":"116", "title":"CNN", "id":"cnn", "parentID":"00070044g%3apublicradio"}, - {"fullTitle":"117 - HLN", "channelNum":"117", "title":"HLN", "id":"cnnheadlinenews", "parentID":"00070044g%3apublicradio"}, - {"fullTitle":"118 - MSNBC", "channelNum":"118", "title":"MSNBC", "id":"8367", "parentID":"00070044g%3apublicradio"}, - {"fullTitle":"119 - Bloomberg Radio", "channelNum":"119", "title":"Bloomberg Radio", "id":"bloombergradio", "parentID":"00070044g%3apublicradio"}, - {"fullTitle":"120 - BBC World Service", "channelNum":"120", "title":"BBC World Service", "id":"bbcworld", "parentID":"00070044g%3apublicradio"}, - {"fullTitle":"121 - SiriusXM Insight", "channelNum":"121", "title":"SiriusXM Insight", "id":"8183", "parentID":"00070044g%3apublicradio"}, - {"fullTitle":"122 - NPR Now", "channelNum":"122", "title":"NPR Now", "id":"nprnow", "parentID":"00070044g%3apublicradio"}, - {"fullTitle":"123 - PRX Public Radio", "channelNum":"123", "title":"PRX Public Radio", "id":"8239", "parentID":"00070044g%3apublicradio"}, - {"fullTitle":"124 - POTUS Politics", "channelNum":"124", "title":"POTUS Politics", "id":"indietalk", "parentID":"00070044g%3apolitical"}, - {"fullTitle":"125 - SiriusXM Patriot", "channelNum":"125", "title":"SiriusXM Patriot", "id":"siriuspatriot", "parentID":"00070044g%3apolitical"}, - {"fullTitle":"126 - SiriusXM Urban View", "channelNum":"126", "title":"SiriusXM Urban View", "id":"8238", "parentID":"00070044g%3apolitical"}, - {"fullTitle":"127 - SiriusXM Progress", "channelNum":"127", "title":"SiriusXM Progress", "id":"siriusleft", "parentID":"00070044g%3apolitical"}, - {"fullTitle":"147 - RURAL Radio", "channelNum":"147", "title":"RURAL Radio", "id":"9367", "parentID":"00070044g%3apublicradio"}, - {"fullTitle":"155 - CNN en Español", "channelNum":"155", "title":"CNN en Español", "id":"cnnespanol", "parentID":"00070044g%3apublicradio"}, - {"fullTitle":"169 - CBC Radio One", "channelNum":"169", "title":"CBC Radio One", "id":"cbcradioone", "parentID":"00070044g%3apublicradio"}, - {"fullTitle":"450 - FOX News Talk", "channelNum":"450", "title":"FOX News Talk", "id":"9370", "parentID":"00070044g%3apolitical"}, - {"fullTitle":"455 - C-SPAN Radio", "channelNum":"455", "title":"C-SPAN Radio", "id":"8237", "parentID":"00070044g%3apublicradio"}, - {"fullTitle":"77 - KIDZ BOP Radio", "channelNum":"77", "title":"KIDZ BOP Radio", "id":"9366", "parentID":"00070044g%3akids"}, - {"fullTitle":"78 - Kids Place Live", "channelNum":"78", "title":"Kids Place Live", "id":"8216", "parentID":"00070044g%3akids"}, - {"fullTitle":"79 - Radio Disney", "channelNum":"79", "title":"Radio Disney", "id":"radiodisney", "parentID":"00070044g%3akids"}, - {"fullTitle":"795 - France 24", "channelNum":"795", "title":"France 24", "id":"9417", "parentID":"00070044g%3apublicradio"}, - {"fullTitle":"796 - TheBlaze Radio Network", "channelNum":"796", "title":"TheBlaze Radio Network", "id":"9355", "parentID":"00070044g%3apolitical"}, - {"fullTitle":"797 - SiriusXM Patriot Plus", "channelNum":"797", "title":"SiriusXM Patriot Plus", "id":"8235", "parentID":"00070044g%3apolitical"}, - {"fullTitle":"798 - SiriusXM Progress Plus", "channelNum":"798", "title":"SiriusXM Progress Plus", "id":"9137", "parentID":"00070044g%3apolitical"}, - {"fullTitle":"102 - Radio Andy", "channelNum":"102", "title":"Radio Andy", "id":"9409", "parentID":"00070044g%3aentertainment"}, - {"fullTitle":"105 - EW Radio", "channelNum":"105", "title":"EW Radio", "id":"9351", "parentID":"00070044g%3aentertainment"}, - {"fullTitle":"106 - SiriusXM 106", "channelNum":"106", "title":"SiriusXM 106", "id":"siriusoutq", "parentID":"00070044g%3aentertainment"}, - {"fullTitle":"108 - Today Show Radio", "channelNum":"108", "title":"Today Show Radio", "id":"9390", "parentID":"00070044g%3aentertainment"}, - {"fullTitle":"109 - SiriusXM Stars", "channelNum":"109", "title":"SiriusXM Stars", "id":"siriusstars", "parentID":"00070044g%3aentertainment"}, - {"fullTitle":"11 - KIIS-Los Angeles", "channelNum":"11", "title":"KIIS-Los Angeles", "id":"8241", "parentID":"00070044g%3amore"}, - {"fullTitle":"110 - Doctor Radio", "channelNum":"110", "title":"Doctor Radio", "id":"doctorradio", "parentID":"00070044g%3aentertainment"}, - {"fullTitle":"111 - Business Radio", "channelNum":"111", "title":"Business Radio", "id":"9359", "parentID":"00070044g%3aentertainment"}, - {"fullTitle":"12 - Z100/NY", "channelNum":"12", "title":"Z100/NY", "id":"8242", "parentID":"00070044g%3amore"}, - {"fullTitle":"128 - Joel Osteen Radio", "channelNum":"128", "title":"Joel Osteen Radio", "id":"9392", "parentID":"00070044g%3aentertainment"}, - {"fullTitle":"129 - The Catholic Channel", "channelNum":"129", "title":"The Catholic Channel", "id":"thecatholicchannel", "parentID":"00070044g%3areligion"}, - {"fullTitle":"130 - EWTN Radio", "channelNum":"130", "title":"EWTN Radio", "id":"ewtnglobal", "parentID":"00070044g%3areligion"}, - {"fullTitle":"131 - Family Talk", "channelNum":"131", "title":"Family Talk", "id":"8307", "parentID":"00070044g%3areligion"}, - {"fullTitle":"141 - HUR Voices", "channelNum":"141", "title":"HUR Voices", "id":"9129", "parentID":"00070044g%3amore"}, - {"fullTitle":"142 - HBCU", "channelNum":"142", "title":"HBCU", "id":"9130", "parentID":"00070044g%3amore"}, - {"fullTitle":"143 - BYUradio", "channelNum":"143", "title":"BYUradio", "id":"9131", "parentID":"00070044g%3amore"}, - {"fullTitle":"146 - Road Dog Trucking", "channelNum":"146", "title":"Road Dog Trucking", "id":"roaddogtrucking", "parentID":"00070044g%3aentertainment"}, - {"fullTitle":"148 - RadioClassics", "channelNum":"148", "title":"RadioClassics", "id":"radioclassics", "parentID":"00070044g%3aentertainment"}, - {"fullTitle":"168 - Canada Laughs", "channelNum":"168", "title":"Canada Laughs", "id":"8259", "parentID":"00070044g%3acomedy"}, - {"fullTitle":"206 - Opie Radio", "channelNum":"206", "title":"Opie Radio", "id":"8184", "parentID":"00070044g%3aentertainment"}, - {"fullTitle":"400 - Carlins Corner", "channelNum":"400", "title":"Carlins Corner", "id":"9181", "parentID":"00070044g%3acomedy"}, - {"fullTitle":"790 - SXM Limited Edition 11", "channelNum":"790", "title":"SXM Limited Edition 11", "id":"9405", "parentID":"00070044g%3aentertainment"}, - {"fullTitle":"791 - Vivid Radio", "channelNum":"791", "title":"Vivid Radio", "id":"8369", "parentID":"00070044g%3aentertainment"}, - {"fullTitle":"794 - SiriusXM Preview", "channelNum":"794", "title":"SiriusXM Preview", "id":"0000", "parentID":"00070044g%3aentertainment"}, {"fullTitle":"94 - SiriusXM Comedy Greats", "channelNum":"94", "title":"SiriusXM Comedy Greats", "id":"9408", "parentID":"00070044g%3acomedy"}, {"fullTitle":"95 - Comedy Central Radio", "channelNum":"95", "title":"Comedy Central Radio", "id":"9356", "parentID":"00070044g%3acomedy"}, {"fullTitle":"96 - The Foxxhole", "channelNum":"96", "title":"The Foxxhole", "id":"thefoxxhole", "parentID":"00070044g%3acomedy"}, {"fullTitle":"97 - Comedy Roundup", "channelNum":"97", "title":"Comedy Roundup", "id":"bluecollarcomedy", "parentID":"00070044g%3acomedy"}, {"fullTitle":"98 - Laugh USA", "channelNum":"98", "title":"Laugh USA", "id":"laughbreak", "parentID":"00070044g%3acomedy"}, - {"fullTitle":"99 - Raw Dog Comedy Hits", "channelNum":"99", "title":"Raw Dog Comedy Hits", "id":"rawdog", "parentID":"00070044g%3acomedy"}, - {"fullTitle":"144 - Korea Today", "channelNum":"144", "title":"Korea Today", "id":"9132", "parentID":"00070044g%3amore"}, - {"fullTitle":"152 - En Vivo", "channelNum":"152", "title":"En Vivo", "id":"9135", "parentID":"00070044g%3amore"}, - {"fullTitle":"153 - Cristina Radio", "channelNum":"153", "title":"Cristina Radio", "id":"9134", "parentID":"00070044g%3amore"}, - {"fullTitle":"154 - American Latino Radio", "channelNum":"154", "title":"American Latino Radio", "id":"9133", "parentID":"00070044g%3amore"}, - {"fullTitle":"162 - CBC Radio 3", "channelNum":"162", "title":"CBC Radio 3", "id":"cbcradio3", "parentID":"00070044g%3acanadian"}, - {"fullTitle":"163 - Ici Musique Chansons", "channelNum":"163", "title":"Ici Musique Chansons", "id":"8245", "parentID":"00070044g%3acanadian"}, - {"fullTitle":"165 - Multicultural Radio", "channelNum":"165", "title":"Multicultural Radio", "id":"9358", "parentID":"00070044g%3acanadian"}, - {"fullTitle":"166 - Ici FrancoCountry", "channelNum":"166", "title":"Ici FrancoCountry", "id":"rockvelours", "parentID":"00070044g%3acanadian"}, - {"fullTitle":"167 - Canada Talks", "channelNum":"167", "title":"Canada Talks", "id":"9172", "parentID":"00070044g%3acanadian"}, - {"fullTitle":"170 - Ici Première", "channelNum":"170", "title":"Ici Première", "id":"premiereplus", "parentID":"00070044g%3acanadian"}, - {"fullTitle":"171 - CBC Country", "channelNum":"171", "title":"CBC Country", "id":"bandeapart", "parentID":"00070044g%3acanadian"}, - {"fullTitle":"172 - Canada 360 by AMI", "channelNum":"172", "title":"Canada 360 by AMI", "id":"8248", "parentID":"00070044g%3acanadian"}, - {"fullTitle":"173 - The Verge", "channelNum":"173", "title":"The Verge", "id":"8244", "parentID":"00070044g%3acanadian"}, - {"fullTitle":"174 - Influence Franco", "channelNum":"174", "title":"Influence Franco", "id":"8246", "parentID":"00070044g%3acanadian"}, - {"fullTitle":"470 - El Paisa", "channelNum":"470", "title":"El Paisa", "id":"9414", "parentID":"00070044g%3amore"}, - {"fullTitle":"758 - Iceberg", "channelNum":"758", "title":"Iceberg", "id":"icebergradio", "parentID":"00070044g%3acanadian"}, - {"fullTitle":"759 - Attitude Franco", "channelNum":"759", "title":"Attitude Franco", "id":"energie2", "parentID":"00070044g%3acanadian"} -] \ No newline at end of file + {"fullTitle":"99 - Raw Dog Comedy Hits", "channelNum":"99", "title":"Raw Dog Comedy Hits", "id":"rawdog", "parentID":"00070044g%3acomedy"} +] From e73ef7d1a387c2547235cf3b4fe0e7682ac13a67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thorsten=20Vo=C3=9F?= Date: Tue, 9 Jan 2018 00:19:26 +0100 Subject: [PATCH 068/123] Support for Amazon Music and a little fix for Apple Music (#580) * Don't skip first track when using Apple Music /now/ if the queue was empty before. * Added (hacked in) support for Amazon Music. Works for me, hope it's OK. It's based on the Apple Music implementation. * Added some documentation for Amazon Music and how to search for song and album IDs with iTunes or Amazon Music App. * Fixed MD errors of the documentation. * More MD typos in README.md * mark down **hates** me * Type in README.md * Updated docu --- README.md | 34 ++++++++++++++++-- lib/actions/amazonMusic.js | 74 ++++++++++++++++++++++++++++++++++++++ lib/actions/appleMusic.js | 2 +- 3 files changed, 106 insertions(+), 4 deletions(-) create mode 100644 lib/actions/amazonMusic.js diff --git a/README.md b/README.md index 05f40ce5..6114522a 100644 --- a/README.md +++ b/README.md @@ -775,7 +775,7 @@ adjust crossover frequency in hz. Official values are 50 through 110 in incremen `/TV%20Room/sub/polarity/1` Switch "placement adjustment" or more commonly known as phase. 0 = 0°, 1 = 180° -Spotify and Apple Music (Experimental) +Spotify, Apple Music and Amazon Music (Experimental) ---------------------- Allows you to perform your own external searches for Apple Music or Spotify songs or albums and play a specified song or track ID. The Music Search funtionality outlined further below performs a search of its own and plays the specified music. @@ -791,12 +791,40 @@ The following endpoints are available: # Apple Music /RoomName/applemusic/{now,next,queue}/song:{songID} /RoomName/applemusic/{now,next,queue}/album:{albumID} + +# Amazon Music +/RoomName/amazonmusic/{now,next,queue}/song:{songID} +/RoomName/amazonmusic/{now,next,queue}/album:{albumID} ``` -You can find Apple Music song and album IDs via the [iTunes Search +It only handles a single **spotify** account currently. It will probably use the first account added on your system. + +You can find **Apple Music** song and album IDs via the [iTunes Search API](https://affiliate.itunes.apple.com/resources/documentation/itunes-store-web-service-search-api/). -It only handles a single spotify account currently. It will probably use the first account added on your system. +You can also use iTunes to figure out song and album IDs. Right click on a song or album and select "Share" -> "Copy Link". You can do this when you searched within Apple Music or from your media library as long as the song is available in Apple Music. + +Have a look at the link you just copied. + +*If you shared the link to a song:* +The format is: https://itunes.apple.com/de/album/{songName}/{albumID}?i={songID} +> eg: https://itunes.apple.com/de/album/blood-of-my-enemies/355363490?i=355364259 + +*If you shared the link to an album:* +The format is: https://itunes.apple.com/de/album/{albumName}/{albumID} +> eg: https://itunes.apple.com/de/album/f-g-restless/355363490 + +To find **Amazon Music** song and album IDs you can use the Amazon Music App, search for a song or an album and share a link. + +Look at the link you just shared. This works with Amazon Music Prime as well as with Amazon Music Prime which is included in your Amazon Prime membership. + +*If you shared the link to a song:* +The format is: https://music.amazon.de/albums/{albumID}?trackAsin={songID}&ref=dm_sh_d74d-4daa-dmcp-63cb-e8747&musicTerritory=DE&marketplaceId=A1PA6795UKMFR9 +> eg: https://music.amazon.de/albums/B0727SH7LW?trackAsin=B071918VCR&ref=dm_sh_d74d-4daa-dmcp-63cb-e8747&musicTerritory=DE&marketplaceId=A1PA6795UKMFR9 + +*If you shared the link to an album:* +The format is: https://music.amazon.de/albums/{albumID}?ref=dm_sh_97aa-255b-dmcp-c6ba-4ff00&musicTerritory=DE&marketplaceId=A1PA6795UKMFR9 +> eg: https://music.amazon.de/albums/B0727SH7LW?ref=dm_sh_97aa-255b-dmcp-c6ba-4ff00&musicTerritory=DE&marketplaceId=A1PA6795UKMFR9 SiriusXM diff --git a/lib/actions/amazonMusic.js b/lib/actions/amazonMusic.js new file mode 100644 index 00000000..dcd84661 --- /dev/null +++ b/lib/actions/amazonMusic.js @@ -0,0 +1,74 @@ +'use strict'; +function getMetadata(id, parentUri, type, title) { +return ` + + "${title}" + ${type} + SA_RINCON51463_X_#Svc51463-0-Token + +`; +} + +function getSongUri(id) { + return `x-sonosapi-hls-static:catalog%2ftracks%2f${id}%2f%3falbumAsin%3dB01JDKZWK0?sid=201&flags=0&sn=4`; +} + +function getAlbumUri(id) { + return `x-rincon-cpcontainer:1004206ccatalog%2falbums%2f${id}%2f%23album_desc?sid=201&flags=8300&sn=4`; +} + +const uriTemplates = { + song: getSongUri, + album: getAlbumUri +}; + +const CLASSES = { + song: 'object.container.album.musicAlbum.#AlbumView', + album: 'object.container.album.musicAlbum' +}; + +const METADATA_URI_STARTERS = { + song: '10030000catalog%2ftracks%2f', + album: '1004206ccatalog' +}; + +const METADATA_URI_ENDINGS = { + song: '%2f%3falbumAsin%3d', + album: '%2f%23album_desc' +}; + + +const PARENTS = { + song: '1004206ccatalog%2falbums%2f', + album: '10052064catalog%2fartists%2f' +}; + +function amazonMusic(player, values) { + const action = values[0]; + const track = values[1]; + const type = track.split(':')[0]; + const trackID = track.split(':')[1]; + + var nextTrackNo = 0; + + const metadataID = METADATA_URI_STARTERS[type] + encodeURIComponent(trackID) + METADATA_URI_ENDINGS[type]; + + const metadata = getMetadata(metadataID, PARENTS[type], CLASSES[type], ''); + const uri = uriTemplates[type](encodeURIComponent(trackID)); + + if (action == 'queue') { + return player.coordinator.addURIToQueue(uri, metadata); + } else if (action == 'now') { + nextTrackNo = player.coordinator.state.trackNo + 1; + return player.coordinator.addURIToQueue(uri, metadata, true, nextTrackNo) + .then(() => { if (nextTrackNo != 1) player.coordinator.nextTrack() }) + .then(() => player.coordinator.play()); + } else if (action == 'next') { + nextTrackNo = player.coordinator.state.trackNo + 1; + return player.coordinator.addURIToQueue(uri, metadata, true, nextTrackNo); + } +} + +module.exports = function (api) { + api.registerAction('amazonmusic', amazonMusic); +}; diff --git a/lib/actions/appleMusic.js b/lib/actions/appleMusic.js index 8992fc0b..bb5690ac 100644 --- a/lib/actions/appleMusic.js +++ b/lib/actions/appleMusic.js @@ -49,7 +49,7 @@ function appleMusic(player, values) { } else if (action == 'now') { nextTrackNo = player.coordinator.state.trackNo + 1; return player.coordinator.addURIToQueue(uri, metadata, true, nextTrackNo) - .then(() => player.coordinator.nextTrack()) + .then(() => { if (nextTrackNo != 1) player.coordinator.nextTrack() }) .then(() => player.coordinator.play()); } else if (action == 'next') { nextTrackNo = player.coordinator.state.trackNo + 1; From 7908a4b5f14b3ad7c9fc2e9dba687d6f214294c7 Mon Sep 17 00:00:00 2001 From: Mikkel Flindt Heisterberg Date: Thu, 25 Jan 2018 16:00:02 +0100 Subject: [PATCH 069/123] Wrap decoding of player name in try/catch to avoid server from crashing on malformed uri (#590) --- lib/sonos-http-api.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/sonos-http-api.js b/lib/sonos-http-api.js index 6f776965..460bbf8a 100644 --- a/lib/sonos-http-api.js +++ b/lib/sonos-http-api.js @@ -60,8 +60,15 @@ function HttpAPI(discovery, settings) { } const params = req.url.substring(1).split('/'); - - let player = discovery.getPlayer(decodeURIComponent(params[0])); + + // parse decode player name considering decode errors + let player; + try { + player = discovery.getPlayer(decodeURIComponent(params[0])); + } catch (error) { + logger.error(`Unable to parse supplied URI component (${params[0]})`, error); + return sendResponse(500, { status: 'error', error: error.message, stack: error.stack }); + } const opt = {}; From e39575986882926da6da65ea0f1c4d19d2f2067f Mon Sep 17 00:00:00 2001 From: MatthewAlner Date: Mon, 2 Apr 2018 22:15:36 +0100 Subject: [PATCH 070/123] Add example for TuneIn to docs (#607) Added an example of how to find / what an ID looks like --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 6114522a..308ee1b1 100644 --- a/README.md +++ b/README.md @@ -872,6 +872,12 @@ Will set and start playing given Station id Will set without start playing given Station id ``` +For example to play Radio 6 Music - [tunein.com/radio/s44491](https://tunein.com/radio/s44491) + +``` +/RoomName/tunein/play/44491 +note the droping of the 's' in 's44491' +``` Music Search and Play ---------------------- From 1b0c2ee67f5c5aab4fbe5cb84940967da7a709ac Mon Sep 17 00:00:00 2001 From: Gary Caldwell Date: Mon, 2 Jul 2018 07:50:03 -0700 Subject: [PATCH 071/123] hacked around spotify issues Start to fixing Pandora Fixing thumbsup and thumbsdown fix: Parse querystring properly --- lib/actions/pandora.js | 38 +++++++++++++++++++------------- lib/music_services/spotifyDef.js | 2 +- package.json | 4 ++-- 3 files changed, 26 insertions(+), 18 deletions(-) diff --git a/lib/actions/pandora.js b/lib/actions/pandora.js index dc922742..2f498ed3 100644 --- a/lib/actions/pandora.js +++ b/lib/actions/pandora.js @@ -1,24 +1,25 @@ 'use strict'; -const promise = require('request-promise'); -const Anesidora = require("anesidora"); +const url = require('url'); +const querystring = require('querystring'); +const Anesidora = require('anesidora'); const Fuse = require('fuse.js'); const settings = require('../../settings'); -function getPandoraMetadata(id, title, auth) { +function getPandoraMetadata(id, title, serviceType) { return ` - ${title}object.item.audioItem.audioBroadcast - SA_RINCON3_${auth}`; + ${title}object.item.audioItem.audioBroadcast.#station + SA_RINCON${serviceType}_X_#Svc${serviceType}-0-Token
    `; } function getPandoraUri(id, title, albumart) { - if (albumart == undefined) { - return `pndrradio:${id}?sn=2`; - } else { - return `pndrradio:${id}?sn=2,"title":"${title}","albumArtUri":"${albumart}"`; - } + return `x-sonosapi-radio:ST%3a${id}?sid=236&flags=8300&sn=1`; } +function parseQuerystring(uri) { + const parsedUri = url.parse(uri); + return querystring.parse(parsedUri.query); +} function pandora(player, values) { const cmd = values[0]; @@ -53,6 +54,8 @@ function pandora(player, values) { var uri = ''; var metadata = ''; + var sid = player.system.getServiceId('Pandora'); + return userLogin() .then(() => pandoraAPI("user.getStationList", {"includeStationArtUrl" : true})) .then((stationList) => { @@ -87,13 +90,13 @@ function pandora(player, values) { const station = results[0]; if (station.type == undefined) { uri = getPandoraUri(station.stationId, station.stationName, station.artUrl); - metadata = getPandoraMetadata(station.stationId, station.stationName, settings.pandora.username); + metadata = getPandoraMetadata(station.stationId, station.stationName, player.system.getServiceType('Pandora')); return Promise.resolve(); } else { return pandoraAPI("station.createStation", {"musicToken":station.stationId, "musicType":station.type}) .then((stationInfo) => { uri = getPandoraUri(stationInfo.stationId); - metadata = getPandoraMetadata(stationInfo.stationId, stationInfo.stationName, settings.pandora.username); + metadata = getPandoraMetadata(stationInfo.stationId, stationInfo.stationName, player.system.getServiceType('Pandora')); return Promise.resolve(); }); } @@ -112,11 +115,16 @@ function pandora(player, values) { if (cmd == 'play') { return playPandora(player, values[1]); } if ((cmd == 'thumbsup')||(cmd == 'thumbsdown')) { + + var sid = player.system.getServiceId('Pandora'); const uri = player.state.currentTrack.uri; + + const parameters = parseQuerystring(uri); - if (uri.startsWith('pndrradio-http')) { - const stationToken = uri.substring(uri.search('&x=') + 3); - const trackToken = uri.substring(uri.search('&m=') + 3,uri.search('&f=')); + if (uri.startsWith('x-sonosapi-radio') && parameters.sid == sid && player.state.currentTrack.trackUri) { + const trackUri = player.state.currentTrack.trackUri; + const trackToken = trackUri.substring(trackUri.search('x-sonos-http:') + 13, trackUri.search('%3a%3aST%3a')); + const stationToken = trackUri.substring(trackUri.search('%3a%3aST%3a') + 11, trackUri.search('%3a%3aRINCON')); const up = (cmd == 'thumbsup'); return userLogin() diff --git a/lib/music_services/spotifyDef.js b/lib/music_services/spotifyDef.js index 33036fcc..bcb6df1f 100644 --- a/lib/music_services/spotifyDef.js +++ b/lib/music_services/spotifyDef.js @@ -146,7 +146,7 @@ function setService(player, p_accountId, p_accountSN, p_country) sid = player.system.getServiceId('Spotify'); serviceType = player.system.getServiceType('Spotify'); accountId = p_accountId; - accountSN = p_accountSN; + accountSN = 14; // GACALD: Hack to fix Spotify p_accountSN; country = p_country; } diff --git a/package.json b/package.json index 98461093..016695d5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sonos-http-api", - "version": "1.6.0", + "version": "1.6.2", "description": "A simple node app for controlling a Sonos system with basic HTTP requests", "scripts": { "start": "node server.js", @@ -22,7 +22,7 @@ "music-metadata": "^0.8.4", "node-static": "^0.7.9", "request-promise": "~1.0.2", - "sonos-discovery": "https://github.com/jishi/node-sonos-discovery/archive/v1.6.0.tar.gz", + "sonos-discovery": "https://github.com/jishi/node-sonos-discovery/archive/v1.7.0.tar.gz", "wav-file-info": "0.0.8" }, "engines": { From ec8ff5361ba32d0bc0ac7f173f96e11ef8638825 Mon Sep 17 00:00:00 2001 From: Jimmy Shimizu Date: Wed, 1 Aug 2018 21:43:15 +0200 Subject: [PATCH 072/123] fix: Resolves #635, #633 Pause before applying preset for announcement Don't crash on Airplay streaming --- lib/helpers/preset-announcement.js | 4 +- package-lock.json | 1092 ++++++++++++++-------------- package.json | 6 +- 3 files changed, 550 insertions(+), 552 deletions(-) diff --git a/lib/helpers/preset-announcement.js b/lib/helpers/preset-announcement.js index 345a8ef4..20fdc696 100644 --- a/lib/helpers/preset-announcement.js +++ b/lib/helpers/preset-announcement.js @@ -72,7 +72,9 @@ function announcePreset(system, uri, preset, duration) { const restoreTimeout = duration + 2000; const coordinator = system.getPlayer(preset.players[0].roomName); - return system.applyPreset(simplifiedPreset) + return coordinator.pause() + .then(() => system.applyPreset(simplifiedPreset)) + .catch(() => system.applyPreset(simplifiedPreset)) .then(() => { if (hasReachedCorrectTopology(system.zones)) return; return oneGroupPromise; diff --git a/package-lock.json b/package-lock.json index 931f99ce..90846171 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,18 +1,13 @@ { "name": "sonos-http-api", - "version": "1.4.5", + "version": "1.6.3", "lockfileVersion": 1, "requires": true, "dependencies": { - "@types/es6-promise": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/@types/es6-promise/-/es6-promise-0.0.33.tgz", - "integrity": "sha512-HKJFVLCGrWQ/1unEw8JdaTxu6n3EUxmwTxJ6D0O1x0gD8joCsgoTWxEgevb7fp2XIogNjof3KEd+3bJoGne/nw==" - }, "acorn": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.1.2.tgz", - "integrity": "sha512-o96FZLJBPY1lvTuJylGA9Bk3t/GKPPJG8H0ydQQl01crzwJgspa4AEIq/pVTXigmK0PHVQhiAtn8WMBLL9D2WA==", + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.1.tgz", + "integrity": "sha512-d+nbxBUGKg7Arpsvbnlq61mc12ek3EY8EQldM3GPAhWJ1UVxC6TDGbIvUMNU6obBX3i1+ptCIzV4vq0gFPEGVQ==", "dev": true }, "acorn-jsx": { @@ -33,18 +28,20 @@ } }, "ajv": { - "version": "4.11.8", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", - "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=", + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", + "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", "requires": { "co": "4.6.0", - "json-stable-stringify": "1.0.1" + "fast-deep-equal": "1.1.0", + "fast-json-stable-stringify": "2.0.0", + "json-schema-traverse": "0.3.1" } }, "ajv-keywords": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-2.1.0.tgz", - "integrity": "sha1-opbhf3v658HOT34N5T0pyzIWLfA=", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-2.1.1.tgz", + "integrity": "sha1-YXmX/F9gV2iUxDX5QNgZ4TW4B2I=", "dev": true }, "anesidora": { @@ -52,14 +49,14 @@ "resolved": "https://registry.npmjs.org/anesidora/-/anesidora-1.2.1.tgz", "integrity": "sha1-/ZWdrLiPx6im5xE+OA8/rQmTipo=", "requires": { - "request": "2.81.0", - "underscore": "1.8.3" + "request": "2.87.0", + "underscore": "1.9.1" } }, "ansi-escapes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.0.0.tgz", - "integrity": "sha512-O/klc27mWNUigtv0F8NJWbLF00OcegQalkqKURWdosW08YZKi4m6CnSUSvIZG1otNJbTWhN01Hhz389DW7mvDQ==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz", + "integrity": "sha512-UgAb8H9D41AQnu/PbWlCofQVcnV4Gs2bBJi9eZPxfU/hgglFh3SMDMENRIqdr7H6XFnXdoknctFByVsCOotTVw==", "dev": true }, "ansi-regex": { @@ -75,9 +72,9 @@ "dev": true }, "argparse": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.9.tgz", - "integrity": "sha1-c9g7wmP4bpf4zE9rrhsOkKfSLIY=", + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, "requires": { "sprintf-js": "1.0.3" @@ -110,9 +107,17 @@ "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=" }, "assert-plus": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz", - "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=" + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + }, + "async": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.1.tgz", + "integrity": "sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==", + "requires": { + "lodash": "4.17.10" + } }, "asynckit": { "version": "0.4.0", @@ -120,38 +125,37 @@ "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, "aws-sdk": { - "version": "2.114.0", - "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.114.0.tgz", - "integrity": "sha1-NckZ6N4c/ZbtplzYpmGZcyR7tzc=", + "version": "2.284.1", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.284.1.tgz", + "integrity": "sha512-/fV2RGGhL13LH7xjccmAh/uoNEa3z+MWHRmHq+Arj2hKgpPoRNCC+8lLIAhxKcPLdJt/G9rcm2EnDqT9E/1aSA==", "requires": { "buffer": "4.9.1", - "crypto-browserify": "1.0.9", "events": "1.1.1", + "ieee754": "1.1.8", "jmespath": "0.15.0", "querystring": "0.2.0", "sax": "1.2.1", "url": "0.10.3", - "uuid": "3.0.1", - "xml2js": "0.4.17", - "xmlbuilder": "4.2.1" + "uuid": "3.1.0", + "xml2js": "0.4.19" }, "dependencies": { "uuid": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.0.1.tgz", - "integrity": "sha1-ZUS7ot/ajBzxfmKaOjBeK7H+5sE=" + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz", + "integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g==" } } }, "aws-sign2": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz", - "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=" + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" }, "aws4": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz", - "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=" + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.7.0.tgz", + "integrity": "sha512-32NDda82rhwD9/JBCCkB+MRYDp0oSvlo2IL6rQWA10PQi7tDUM3eqMSltXmY+Oyl/7N3P3qNtAlv7X0d9bI28w==" }, "babel-code-frame": { "version": "6.26.0", @@ -195,9 +199,9 @@ "dev": true }, "base64-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.2.1.tgz", - "integrity": "sha512-dwVUVIXsBZXwTuwnXI9RK8sBmgq09NDHzyR9SAph9eqk76gKK2JSQmZARC2zRC81JC2QTtxD0ARU5qTS25gIGw==" + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.0.tgz", + "integrity": "sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw==" }, "basic-auth": { "version": "1.1.0", @@ -205,9 +209,9 @@ "integrity": "sha1-RSIe5Cn37h5QNb4/UVM/HN/SmIQ=" }, "bcrypt-pbkdf": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", - "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", "optional": true, "requires": { "tweetnacl": "0.14.5" @@ -218,18 +222,10 @@ "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-2.11.0.tgz", "integrity": "sha1-U0uQM8AiyVecVro7Plpcqvu2UOE=" }, - "boom": { - "version": "2.10.1", - "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", - "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=", - "requires": { - "hoek": "2.16.3" - } - }, "brace-expansion": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", - "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=", + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, "requires": { "balanced-match": "1.0.0", @@ -241,11 +237,17 @@ "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", "requires": { - "base64-js": "1.2.1", + "base64-js": "1.3.0", "ieee754": "1.1.8", "isarray": "1.0.0" } }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, "builtin-modules": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", @@ -273,36 +275,42 @@ "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" }, "chalk": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.1.0.tgz", - "integrity": "sha512-LUHGS/dge4ujbXMJrnihYMcL4AoOweGnw9Tp3kQuqy1Kx5c1qKjqvMJZ6nVJPMWJtKCTN72ZogH3oeSO9g9rXQ==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", "dev": true, "requires": { - "ansi-styles": "3.2.0", + "ansi-styles": "3.2.1", "escape-string-regexp": "1.0.5", - "supports-color": "4.4.0" + "supports-color": "5.4.0" }, "dependencies": { "ansi-styles": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", - "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "color-convert": "1.9.0" + "color-convert": "1.9.2" } }, "supports-color": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.4.0.tgz", - "integrity": "sha512-rKC3+DyXWgK0ZLKwmRsrkyHVZAjNkfzeehuFWdGGcqGDTZFH73+RH6S/RDAAxl9GusSjZSUWYLmT9N5pzXFOXQ==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", "dev": true, "requires": { - "has-flag": "2.0.0" + "has-flag": "3.0.0" } } } }, + "chardet": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz", + "integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=", + "dev": true + }, "circular-json": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", @@ -330,7 +338,7 @@ "integrity": "sha1-syY8EaCJsDlhhaG3q5BNkPAq1Cg=", "requires": { "is-bluebird": "1.0.2", - "shimmer": "1.1.0" + "shimmer": "1.2.0" } }, "co": { @@ -339,29 +347,29 @@ "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" }, "color-convert": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.0.tgz", - "integrity": "sha1-Gsz5fdc5uYO/mU1W/sj5WFNkG3o=", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.2.tgz", + "integrity": "sha512-3NUJZdhMhcdPn8vJ9v2UQJoH0qqoGUkYTgFEPZaPjEtwmmKUfNV46zZmgB2M5M4DCEQHMaCfWHCxiBflLm04Tg==", "dev": true, "requires": { - "color-name": "1.1.3" + "color-name": "1.1.1" } }, "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.1.tgz", + "integrity": "sha1-SxQVMEz1ACjqgWQ2Q72C6gWANok=", "dev": true }, "colors": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz", - "integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=" + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.3.1.tgz", + "integrity": "sha512-jg/vxRmv430jixZrC+La5kMbUWqIg32/JsYNZb94+JEmzceYbWKTsv1OuTp+7EaqiaWRR2tPcykibwCRgclIsw==" }, "combined-stream": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", - "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", + "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", "requires": { "delayed-stream": "1.0.0" } @@ -373,13 +381,14 @@ "dev": true }, "concat-stream": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.0.tgz", - "integrity": "sha1-CqxmL9Ur54lk1VMvaUeE5wEQrPc=", + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", "dev": true, "requires": { + "buffer-from": "1.1.1", "inherits": "2.0.3", - "readable-stream": "2.3.3", + "readable-stream": "2.3.6", "typedarray": "0.0.6" } }, @@ -400,44 +409,23 @@ "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", "dev": true, "requires": { - "lru-cache": "4.1.1", + "lru-cache": "4.1.3", "shebang-command": "1.2.0", - "which": "1.3.0" - } - }, - "cryptiles": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz", - "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=", - "requires": { - "boom": "2.10.1" + "which": "1.3.1" } }, - "crypto-browserify": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-1.0.9.tgz", - "integrity": "sha1-zFRJaF37hesRyYKKzHy4erW7/MA=" - }, "dashdash": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", "requires": { "assert-plus": "1.0.0" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" - } } }, "debug": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, "requires": { "ms": "2.0.0" } @@ -456,7 +444,7 @@ "requires": { "globby": "5.0.0", "is-path-cwd": "1.0.0", - "is-path-in-cwd": "1.0.0", + "is-path-in-cwd": "1.0.1", "object-assign": "4.1.1", "pify": "2.3.0", "pinkie-promise": "2.0.1", @@ -469,37 +457,42 @@ "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" }, "doctrine": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.0.0.tgz", - "integrity": "sha1-xz2NKQnSIpHhoAejlYBNqLZl/mM=", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", "dev": true, "requires": { - "esutils": "2.0.2", - "isarray": "1.0.0" + "esutils": "2.0.2" } }, - "ecc-jsbn": { + "duplexer": { "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", - "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", + "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=" + }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", "optional": true, "requires": { - "jsbn": "0.1.1" + "jsbn": "0.1.1", + "safer-buffer": "2.1.2" } }, "error-ex": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz", - "integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", "dev": true, "requires": { "is-arrayish": "0.2.1" } }, "es6-promise": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.1.1.tgz", - "integrity": "sha512-OaU1hHjgJf+b0NzsxCg7NdIYERD6Hy/PEmFLTjw+b65scuisG3Kt4QoTvJ66BBkPZ581gr0kpoVzKnxniM8nng==" + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.4.tgz", + "integrity": "sha512-/NdNZVJg+uZgtm9eS3O6lrOLYmQag2DjdEXuPaHlZ6RuVqgqaVZfgYCepEIKsLqwdQArOPtC3XzRLqGGfT8KQQ==" }, "escape-string-regexp": { "version": "1.0.5", @@ -508,35 +501,35 @@ "dev": true }, "eslint": { - "version": "4.8.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-4.8.0.tgz", - "integrity": "sha1-Ip7w41Tg5h2DfHqA/fuoJeGZgV4=", + "version": "4.19.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-4.19.1.tgz", + "integrity": "sha512-bT3/1x1EbZB7phzYu7vCr1v3ONuzDtX8WjuM9c0iYxe+cq+pwcKEoQjl7zd3RpC6YOLgnSy3cTN58M2jcoPDIQ==", "dev": true, "requires": { - "ajv": "5.2.3", + "ajv": "5.5.2", "babel-code-frame": "6.26.0", - "chalk": "2.1.0", - "concat-stream": "1.6.0", + "chalk": "2.4.1", + "concat-stream": "1.6.2", "cross-spawn": "5.1.0", "debug": "3.1.0", - "doctrine": "2.0.0", - "eslint-scope": "3.7.1", - "espree": "3.5.1", - "esquery": "1.0.0", - "estraverse": "4.2.0", + "doctrine": "2.1.0", + "eslint-scope": "3.7.3", + "eslint-visitor-keys": "1.0.0", + "espree": "3.5.4", + "esquery": "1.0.1", "esutils": "2.0.2", "file-entry-cache": "2.0.0", "functional-red-black-tree": "1.0.1", "glob": "7.1.2", - "globals": "9.18.0", - "ignore": "3.3.5", + "globals": "11.7.0", + "ignore": "3.3.10", "imurmurhash": "0.1.4", "inquirer": "3.3.0", - "is-resolvable": "1.0.0", - "js-yaml": "3.10.0", - "json-stable-stringify": "1.0.1", + "is-resolvable": "1.1.0", + "js-yaml": "3.12.0", + "json-stable-stringify-without-jsonify": "1.0.1", "levn": "0.3.0", - "lodash": "4.17.4", + "lodash": "4.17.10", "minimatch": "3.0.4", "mkdirp": "0.5.1", "natural-compare": "1.4.0", @@ -544,45 +537,32 @@ "path-is-inside": "1.0.2", "pluralize": "7.0.0", "progress": "2.0.0", + "regexpp": "1.1.0", "require-uncached": "1.0.3", - "semver": "5.4.1", + "semver": "5.5.0", "strip-ansi": "4.0.0", "strip-json-comments": "2.0.1", "table": "4.0.2", "text-table": "0.2.0" - }, - "dependencies": { - "ajv": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.2.3.tgz", - "integrity": "sha1-wG9Zh3jETGsWGrr+NGa4GtGBTtI=", - "dev": true, - "requires": { - "co": "4.6.0", - "fast-deep-equal": "1.0.0", - "json-schema-traverse": "0.3.1", - "json-stable-stringify": "1.0.1" - } - } } }, "eslint-config-airbnb-base": { - "version": "12.0.1", - "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-12.0.1.tgz", - "integrity": "sha512-j9mEf21o09cO+wg4M9cwhvu88E5OxbhC94TjcDsSXfHD25LVmhB6gjy2jUv3JH642TshKKl/HBYzVFCjMd390Q==", + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-12.1.0.tgz", + "integrity": "sha512-/vjm0Px5ZCpmJqnjIzcFb9TKZrKWz0gnuG/7Gfkt0Db1ELJR51xkZth+t14rYdqWgX836XbuxtArbIHlVhbLBA==", "dev": true, "requires": { "eslint-restricted-globals": "0.1.1" } }, "eslint-import-resolver-node": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.1.tgz", - "integrity": "sha512-yUtXS15gIcij68NmXmP9Ni77AQuCN0itXbCc/jWd8C6/yKZaSNXicpC8cgvjnxVdmfsosIXrjpzFq7GcDryb6A==", + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.2.tgz", + "integrity": "sha512-sfmTqJfPSizWu4aymbPr4Iidp5yKm8yDkHp+Ir3YiTHiiDfxh69mOUsmiqW6RZ9zRXFaF64GtYmN7e+8GHBv6Q==", "dev": true, "requires": { "debug": "2.6.9", - "resolve": "1.4.0" + "resolve": "1.8.1" }, "dependencies": { "debug": { @@ -597,9 +577,9 @@ } }, "eslint-module-utils": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.1.1.tgz", - "integrity": "sha512-jDI/X5l/6D1rRD/3T43q8Qgbls2nq5km5KSqiwlyUbGo5+04fXhMKdCPhjwbqAa6HXWaMxj8Q4hQDIh7IadJQw==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.2.0.tgz", + "integrity": "sha1-snA2LNiLGkitMIl2zn+lTphBF0Y=", "dev": true, "requires": { "debug": "2.6.9", @@ -618,21 +598,21 @@ } }, "eslint-plugin-import": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.7.0.tgz", - "integrity": "sha512-HGYmpU9f/zJaQiKNQOVfHUh2oLWW3STBrCgH0sHTX1xtsxYlH1zjLh8FlQGEIdZSdTbUMaV36WaZ6ImXkenGxQ==", + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.13.0.tgz", + "integrity": "sha512-t6hGKQDMIt9N8R7vLepsYXgDfeuhp6ZJSgtrLEDxonpSubyxUZHjhm6LsAaZX8q6GYVxkbT3kTsV9G5mBCFR6A==", "dev": true, "requires": { - "builtin-modules": "1.1.1", "contains-path": "0.1.0", "debug": "2.6.9", "doctrine": "1.5.0", - "eslint-import-resolver-node": "0.3.1", - "eslint-module-utils": "2.1.1", - "has": "1.0.1", - "lodash.cond": "4.5.2", + "eslint-import-resolver-node": "0.3.2", + "eslint-module-utils": "2.2.0", + "has": "1.0.3", + "lodash": "4.17.10", "minimatch": "3.0.4", - "read-pkg-up": "2.0.0" + "read-pkg-up": "2.0.0", + "resolve": "1.8.1" }, "dependencies": { "debug": { @@ -663,48 +643,53 @@ "dev": true }, "eslint-scope": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.1.tgz", - "integrity": "sha1-PWPD7f2gLgbgGkUq2IyqzHzctug=", + "version": "3.7.3", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.3.tgz", + "integrity": "sha512-W+B0SvF4gamyCTmUc+uITPY0989iXVfKvhwtmJocTaYoc/3khEHmEmvfY/Gn9HA9VV75jrQECsHizkNw1b68FA==", "dev": true, "requires": { - "esrecurse": "4.2.0", + "esrecurse": "4.2.1", "estraverse": "4.2.0" } }, + "eslint-visitor-keys": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", + "integrity": "sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ==", + "dev": true + }, "espree": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.1.tgz", - "integrity": "sha1-DJiLirRttTEAoZVK5LqZXd0n2H4=", + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.4.tgz", + "integrity": "sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A==", "dev": true, "requires": { - "acorn": "5.1.2", + "acorn": "5.7.1", "acorn-jsx": "3.0.1" } }, "esprima": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz", - "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true }, "esquery": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.0.tgz", - "integrity": "sha1-z7qLV9f7qT8XKYqKAGoEzaE9gPo=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz", + "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", "dev": true, "requires": { "estraverse": "4.2.0" } }, "esrecurse": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.0.tgz", - "integrity": "sha1-+pVo2Y04I/mkHZHpAtyrnqblsWM=", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", + "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", "dev": true, "requires": { - "estraverse": "4.2.0", - "object-assign": "4.1.1" + "estraverse": "4.2.0" } }, "estraverse": { @@ -719,24 +704,38 @@ "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", "dev": true }, + "event-stream": { + "version": "3.3.4", + "resolved": "http://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", + "integrity": "sha1-SrTJoPWlTbkzi0w02Gv86PSzVXE=", + "requires": { + "duplexer": "0.1.1", + "from": "0.1.7", + "map-stream": "0.1.0", + "pause-stream": "0.0.11", + "split": "0.3.3", + "stream-combiner": "0.0.4", + "through": "2.3.8" + } + }, "events": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=" }, "extend": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", - "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=" + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" }, "external-editor": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.0.5.tgz", - "integrity": "sha512-Msjo64WT5W+NhOpQXh0nOHm+n0RfU1QUwDnKYvJ8dEJ8zlwLrqXNTv5mSUTJpepf41PDJGyhueTw2vNZW+Fr/w==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz", + "integrity": "sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==", "dev": true, "requires": { - "iconv-lite": "0.4.19", - "jschardet": "1.5.1", + "chardet": "0.4.2", + "iconv-lite": "0.4.23", "tmp": "0.0.33" } }, @@ -746,10 +745,14 @@ "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" }, "fast-deep-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz", - "integrity": "sha1-liVqO8l1WV6zbYLpkp0GDYk0Of8=", - "dev": true + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", + "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=" + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" }, "fast-levenshtein": { "version": "2.0.6", @@ -804,23 +807,28 @@ "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" }, "form-data": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz", - "integrity": "sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE=", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz", + "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=", "requires": { "asynckit": "0.4.0", - "combined-stream": "1.0.5", - "mime-types": "2.1.17" + "combined-stream": "1.0.6", + "mime-types": "2.1.19" } }, + "from": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", + "integrity": "sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=" + }, "fs-extra": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.2.tgz", - "integrity": "sha1-+RcExT0bRh+JNFKwwwfZmXZHq2s=", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz", + "integrity": "sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==", "requires": { "graceful-fs": "4.1.11", "jsonfile": "4.0.0", - "universalify": "0.1.1" + "universalify": "0.1.2" } }, "fs.realpath": { @@ -852,13 +860,6 @@ "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", "requires": { "assert-plus": "1.0.0" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" - } } }, "glob": { @@ -876,9 +877,9 @@ } }, "globals": { - "version": "9.18.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", - "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", + "version": "11.7.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.7.0.tgz", + "integrity": "sha512-K8BNSPySfeShBQXsahYB/AbbWruVOTyVpgoIDnl8odPpeSfP2J5QO2oLFFdl2j7GfDCtZj2bMKar2T49itTPCg==", "dev": true }, "globby": { @@ -901,23 +902,23 @@ "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=" }, "har-schema": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-1.0.5.tgz", - "integrity": "sha1-0mMTX0MwfALGAq/I/pWXDAFRNp4=" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" }, "har-validator": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-4.2.1.tgz", - "integrity": "sha1-M0gdDxu/9gDdID11gSpqX7oALio=", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", + "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", "requires": { - "ajv": "4.11.8", - "har-schema": "1.0.5" + "ajv": "5.5.2", + "har-schema": "2.0.0" } }, "has": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.1.tgz", - "integrity": "sha1-hGFzP1OLCDfJNh45qauelwTcLyg=", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", "dev": true, "requires": { "function-bind": "1.1.1" @@ -933,31 +934,15 @@ } }, "has-flag": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", - "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", "dev": true }, - "hawk": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", - "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=", - "requires": { - "boom": "2.10.1", - "cryptiles": "2.0.5", - "hoek": "2.16.3", - "sntp": "1.0.9" - } - }, - "hoek": { - "version": "2.16.3", - "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", - "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=" - }, "hosted-git-info": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.5.0.tgz", - "integrity": "sha512-pNgbURSuab90KbTqvRPsseaTxOJCZBD0a7t+haSN33piP9cCM4l0CqdzAif2hUqm716UovKB2ROmiabGAKVXyg==", + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz", + "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==", "dev": true }, "html-entities": { @@ -966,20 +951,23 @@ "integrity": "sha1-DfKTUfByEWNRXfueVUPl9u7VFi8=" }, "http-signature": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz", - "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", "requires": { - "assert-plus": "0.2.0", + "assert-plus": "1.0.0", "jsprim": "1.4.1", - "sshpk": "1.13.1" + "sshpk": "1.14.2" } }, "iconv-lite": { - "version": "0.4.19", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", - "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==", - "dev": true + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", + "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", + "dev": true, + "requires": { + "safer-buffer": "2.1.2" + } }, "ieee754": { "version": "1.1.8", @@ -987,9 +975,9 @@ "integrity": "sha1-vjPUCsEO8ZJnAfbwii2G+/0a0+Q=" }, "ignore": { - "version": "3.3.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.5.tgz", - "integrity": "sha512-JLH93mL8amZQhh/p6mfQgVBH3M6epNq3DfsXsTSuSrInVjwyYlFE1nv2AgfRCC8PoOhM0jwQ5v8s9LgbK7yGDw==", + "version": "3.3.10", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", + "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", "dev": true }, "imurmurhash": { @@ -1020,13 +1008,13 @@ "integrity": "sha512-h+xtnyk4EwKvFWHrUYsWErEVR+igKtLdchu+o0Z1RL7VU/jVMFbYir2bp6bAj8efFNxWqHX0dIss6fJQ+/+qeQ==", "dev": true, "requires": { - "ansi-escapes": "3.0.0", - "chalk": "2.1.0", + "ansi-escapes": "3.1.0", + "chalk": "2.4.1", "cli-cursor": "2.1.0", "cli-width": "2.2.0", - "external-editor": "2.0.5", + "external-editor": "2.2.0", "figures": "2.0.0", - "lodash": "4.17.4", + "lodash": "4.17.10", "mute-stream": "0.0.7", "run-async": "2.3.0", "rx-lite": "4.0.8", @@ -1069,18 +1057,18 @@ "dev": true }, "is-path-in-cwd": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.0.tgz", - "integrity": "sha1-ZHdYK4IU1gI0YJRWcAO+ip6sBNw=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz", + "integrity": "sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==", "dev": true, "requires": { - "is-path-inside": "1.0.0" + "is-path-inside": "1.0.1" } }, "is-path-inside": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.0.tgz", - "integrity": "sha1-/AbloWg/vaE95mev9xe7wQpI838=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", + "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", "dev": true, "requires": { "path-is-inside": "1.0.2" @@ -1093,13 +1081,10 @@ "dev": true }, "is-resolvable": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.0.0.tgz", - "integrity": "sha1-jfV8YeouPFAUCNEA+wE8+NbgzGI=", - "dev": true, - "requires": { - "tryit": "1.0.3" - } + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", + "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", + "dev": true }, "is-typedarray": { "version": "1.0.0", @@ -1134,13 +1119,13 @@ "dev": true }, "js-yaml": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.10.0.tgz", - "integrity": "sha512-O2v52ffjLa9VeM43J4XocZE//WT9N0IiwDa3KSHH7Tu8CtH+1qM8SIZvnsTh6v+4yFy5KUY3BHUVwjpfAWsjIA==", + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.0.tgz", + "integrity": "sha512-PIt2cnwmPfL4hKNwqeiuz4bKfnzHTBv6HyVgjahA6mPLwPDzjDWrplJBMjHUFxku/N3FlmrbyPclad+I+4mJ3A==", "dev": true, "requires": { - "argparse": "1.0.9", - "esprima": "4.0.0" + "argparse": "1.0.10", + "esprima": "4.0.1" } }, "jsbn": { @@ -1149,12 +1134,6 @@ "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", "optional": true }, - "jschardet": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/jschardet/-/jschardet-1.5.1.tgz", - "integrity": "sha512-vE2hT1D0HLZCLLclfBSfkfTTedhVj0fubHpJBHKwwUWX0nSbhPAfk+SG9rTX95BYNmau8rGFfCeaT6T5OW1C2A==", - "dev": true - }, "json-schema": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", @@ -1163,16 +1142,13 @@ "json-schema-traverse": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", - "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", - "dev": true + "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=" }, - "json-stable-stringify": { + "json-stable-stringify-without-jsonify": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", - "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", - "requires": { - "jsonify": "0.0.0" - } + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true }, "json-stringify-safe": { "version": "5.0.1", @@ -1192,11 +1168,6 @@ "graceful-fs": "4.1.11" } }, - "jsonify": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", - "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=" - }, "jsprim": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", @@ -1206,13 +1177,6 @@ "extsprintf": "1.3.0", "json-schema": "0.2.3", "verror": "1.10.0" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" - } } }, "levn": { @@ -1256,57 +1220,66 @@ } }, "lodash": { - "version": "4.17.4", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", - "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=" + "version": "4.17.10", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", + "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==" }, - "lodash.cond": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/lodash.cond/-/lodash.cond-4.5.2.tgz", - "integrity": "sha1-9HGh2khr5g9quVXRcRVSPdHSVdU=", - "dev": true + "lodash.assign": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz", + "integrity": "sha1-DZnzzNem0mHRm9rrkkUAXShYCOc=" }, "lru-cache": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.1.tgz", - "integrity": "sha512-q4spe4KTfsAS1SUHLO0wz8Qiyf1+vMIAgpRYioFYDMNqKfHQbg+AVDH3i4fvpl71/P1L0dBl+fQi+P37UYf0ew==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.3.tgz", + "integrity": "sha512-fFEhvcgzuIoJVUF8fYr5KR0YqxD238zgObTps31YdADwPPAp82a4M8TrckkWyx7ekNlf9aBcVn81cFwwXngrJA==", "dev": true, "requires": { "pseudomap": "1.0.2", "yallist": "2.1.2" } }, + "map-stream": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", + "integrity": "sha1-5WqpTEyAVaFkBKBnS3jyFffI4ZQ=" + }, "mime": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.0.tgz", - "integrity": "sha512-n9ChLv77+QQEapYz8lV+rIZAW3HhAPW2CXnzb1GN5uMkuczshwvkW7XPsbzU0ZQN3sP47Er2KVkp2p3KyqZKSQ==" + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" }, "mime-db": { - "version": "1.30.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.30.0.tgz", - "integrity": "sha1-dMZD2i3Z1qRTmZY0ZbJtXKfXHwE=" + "version": "1.35.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.35.0.tgz", + "integrity": "sha512-JWT/IcCTsB0Io3AhWUMjRqucrHSPsSf2xKLaRldJVULioggvkJvggZ3VXNNSRkCddE6D+BUI4HEIZIA2OjwIvg==" }, "mime-types": { - "version": "2.1.17", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz", - "integrity": "sha1-Cdejk/A+mVp5+K+Fe3Cp4KsWVXo=", + "version": "2.1.19", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.19.tgz", + "integrity": "sha512-P1tKYHVSZ6uFo26mtnve4HQFE3koh1UWVkp8YUC+ESBHe945xWSoXuHHiGarDqcEZ+whpCDnlNw5LON0kLo+sw==", "requires": { - "mime-db": "1.30.0" + "mime-db": "1.35.0" } }, "mimic-fn": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.1.0.tgz", - "integrity": "sha1-5md4PZLonb00KBi1IwudYqZyrRg=", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", "dev": true }, + "mingo": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/mingo/-/mingo-1.3.3.tgz", + "integrity": "sha1-aSLE0Ufvx3GgFCWixMj3eER4xUY=" + }, "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "dev": true, "requires": { - "brace-expansion": "1.1.8" + "brace-expansion": "1.1.11" } }, "minimist": { @@ -1334,19 +1307,19 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, "music-metadata": { - "version": "0.8.4", - "resolved": "https://registry.npmjs.org/music-metadata/-/music-metadata-0.8.4.tgz", - "integrity": "sha512-vPa0VUnRMgVlui/yoFN+v7pn5mTdgfZSNLS1+AsYLcVxHGMMO/hCly9ICw/vRJTyQuVApEOdiiXr8pxCIa3HuA==", + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/music-metadata/-/music-metadata-0.8.8.tgz", + "integrity": "sha512-Su4vm5YKd3u9N/cRz+96aYNTWnzRXrIbu0B+P+zKuWNYOtbbZ6M6DgmjWEmJYW6NjzhJAxesbrGXJ7K57RFUEg==", "requires": { - "es6-promise": "4.1.1", - "fs-extra": "4.0.2", - "strtok3": "1.3.2", - "then-read-stream": "1.0.3", - "token-types": "0.9.1" + "es6-promise": "4.2.4", + "fs-extra": "4.0.3", + "save": "2.3.2", + "strtok3": "1.5.3", + "then-read-stream": "1.2.1", + "token-types": "0.9.4" } }, "mute-stream": { @@ -1362,12 +1335,12 @@ "dev": true }, "node-static": { - "version": "0.7.9", - "resolved": "https://registry.npmjs.org/node-static/-/node-static-0.7.9.tgz", - "integrity": "sha1-m7afziKB96480fuYPp6g7AzZ/ss=", + "version": "0.7.10", + "resolved": "https://registry.npmjs.org/node-static/-/node-static-0.7.10.tgz", + "integrity": "sha512-bd7zO5hvCWzdglgwz9t82T4mYTEUzEG5pXnSqEzitvmEacusbhl8/VwuCbMaYR9g2PNK5191yBtAEQLJEmQh1A==", "requires": { - "colors": "1.1.2", - "mime": "1.4.0", + "colors": "1.3.1", + "mime": "1.6.0", "optimist": "0.6.1" } }, @@ -1377,10 +1350,10 @@ "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==", "dev": true, "requires": { - "hosted-git-info": "2.5.0", + "hosted-git-info": "2.7.1", "is-builtin-module": "1.0.0", - "semver": "5.4.1", - "validate-npm-package-license": "3.0.1" + "semver": "5.5.0", + "validate-npm-package-license": "3.0.3" } }, "oauth-sign": { @@ -1409,7 +1382,7 @@ "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", "dev": true, "requires": { - "mimic-fn": "1.1.0" + "mimic-fn": "1.2.0" } }, "optimist": { @@ -1450,10 +1423,13 @@ "dev": true }, "p-limit": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.1.0.tgz", - "integrity": "sha1-sH/y2aXYi+yAYDWJWiurZqJ5iLw=", - "dev": true + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "requires": { + "p-try": "1.0.0" + } }, "p-locate": { "version": "2.0.0", @@ -1461,16 +1437,22 @@ "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", "dev": true, "requires": { - "p-limit": "1.1.0" + "p-limit": "1.3.0" } }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true + }, "parse-json": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", "dev": true, "requires": { - "error-ex": "1.3.1" + "error-ex": "1.3.2" } }, "path-exists": { @@ -1509,10 +1491,18 @@ "pify": "2.3.0" } }, + "pause-stream": { + "version": "0.0.11", + "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", + "integrity": "sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=", + "requires": { + "through": "2.3.8" + } + }, "performance-now": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-0.2.0.tgz", - "integrity": "sha1-M+8wxcd9TqIcWlOGnZG1bY8lVeU=" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" }, "pify": { "version": "2.3.0", @@ -1557,9 +1547,9 @@ "dev": true }, "process-nextick-args": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", - "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", "dev": true }, "progress": { @@ -1580,9 +1570,9 @@ "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" }, "qs": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz", - "integrity": "sha1-E+JtKK1rD/qpExLNO/cI7TUecjM=" + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" }, "querystring": { "version": "0.2.0", @@ -1622,47 +1612,51 @@ } }, "readable-stream": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", - "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { "core-util-is": "1.0.2", "inherits": "2.0.3", "isarray": "1.0.0", - "process-nextick-args": "1.0.7", - "safe-buffer": "5.1.1", - "string_decoder": "1.0.3", + "process-nextick-args": "2.0.0", + "safe-buffer": "5.1.2", + "string_decoder": "1.1.1", "util-deprecate": "1.0.2" } }, + "regexpp": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-1.1.0.tgz", + "integrity": "sha512-LOPw8FpgdQF9etWMaAfG/WRthIdXJGYp4mJ2Jgn/2lpkbod9jPn0t9UqN7AxBOKNfzRbYyVfgc7Vk4t/MpnXgw==", + "dev": true + }, "request": { - "version": "2.81.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.81.0.tgz", - "integrity": "sha1-xpKJRqDgbF+Nb4qTM0af/aRimKA=", + "version": "2.87.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.87.0.tgz", + "integrity": "sha512-fcogkm7Az5bsS6Sl0sibkbhcKsnyon/jV1kF3ajGmF0c8HrttdKTPRT9hieOaQHA5HEq6r8OyWOo/o781C1tNw==", "requires": { - "aws-sign2": "0.6.0", - "aws4": "1.6.0", + "aws-sign2": "0.7.0", + "aws4": "1.7.0", "caseless": "0.12.0", - "combined-stream": "1.0.5", - "extend": "3.0.1", + "combined-stream": "1.0.6", + "extend": "3.0.2", "forever-agent": "0.6.1", - "form-data": "2.1.4", - "har-validator": "4.2.1", - "hawk": "3.1.3", - "http-signature": "1.1.1", + "form-data": "2.3.2", + "har-validator": "5.0.3", + "http-signature": "1.2.0", "is-typedarray": "1.0.0", "isstream": "0.1.2", "json-stringify-safe": "5.0.1", - "mime-types": "2.1.17", + "mime-types": "2.1.19", "oauth-sign": "0.8.2", - "performance-now": "0.2.0", - "qs": "6.4.0", - "safe-buffer": "5.1.1", - "stringstream": "0.0.5", - "tough-cookie": "2.3.2", + "performance-now": "2.1.0", + "qs": "6.5.2", + "safe-buffer": "5.1.2", + "tough-cookie": "2.3.4", "tunnel-agent": "0.6.0", - "uuid": "3.1.0" + "uuid": "3.3.2" } }, "request-promise": { @@ -1673,7 +1667,7 @@ "bluebird": "2.11.0", "cls-bluebird": "1.1.3", "lodash": "3.10.1", - "request": "2.81.0" + "request": "2.87.0" }, "dependencies": { "lodash": { @@ -1694,9 +1688,9 @@ } }, "resolve": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.4.0.tgz", - "integrity": "sha512-aW7sVKPufyHqOmyyLzg/J+8606v5nevBgaliIlV7nUpVMsDnoBGV/cbSLNjZAg9q0Cfd/+easKVKQ8vOu8fn1Q==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.8.1.tgz", + "integrity": "sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA==", "dev": true, "requires": { "path-parse": "1.0.5" @@ -1752,9 +1746,25 @@ } }, "safe-buffer": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", - "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "save": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/save/-/save-2.3.2.tgz", + "integrity": "sha1-hZJnS1VlzE4SvG3dnLCfgo4+z30=", + "requires": { + "async": "2.6.1", + "event-stream": "3.3.4", + "lodash.assign": "4.2.0", + "mingo": "1.3.3" + } }, "sax": { "version": "1.2.1", @@ -1762,9 +1772,9 @@ "integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o=" }, "semver": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz", - "integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", + "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", "dev": true }, "shebang-command": { @@ -1783,9 +1793,9 @@ "dev": true }, "shimmer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.1.0.tgz", - "integrity": "sha1-l9c3cTf/u6tCVSLkKf4KqJpIizU=" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.2.0.tgz", + "integrity": "sha512-xTCx2vohXC2EWWDqY/zb4+5Mu28D+HYNSOuFzsyRDRvI/e1ICb69afwaUwfjr+25ZXldbOLyp+iDUZHq8UnTag==" }, "signal-exit": { "version": "3.0.2", @@ -1802,20 +1812,12 @@ "is-fullwidth-code-point": "2.0.0" } }, - "sntp": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz", - "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=", - "requires": { - "hoek": "2.16.3" - } - }, "sonos-discovery": { - "version": "https://github.com/jishi/node-sonos-discovery/archive/v1.5.1.tar.gz", - "integrity": "sha1-spQ5J3EcY3L1pYyuCzf5p833NLA=", + "version": "https://github.com/jishi/node-sonos-discovery/archive/v1.7.1.tar.gz", + "integrity": "sha512-1i8FAbLBajwe0i9nWqVEjvguM8ltfz7Gnc6yaURyCyKYuodoH3KicrLD6xoyi4BYxGyrs7sgTiNDFO0b489erg==", "requires": { "html-entities": "1.0.10", - "xml-flow": "1.0.1" + "xml-flow": "1.0.2" }, "dependencies": { "html-entities": { @@ -1826,26 +1828,45 @@ } }, "spdx-correct": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-1.0.2.tgz", - "integrity": "sha1-SzBz2TP/UfORLwOsVRlJikFQ20A=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.0.0.tgz", + "integrity": "sha512-N19o9z5cEyc8yQQPukRCZ9EUmb4HUpnrmaL/fxS2pBo2jbfcFRVuFZ/oFC+vZz0MNNk0h80iMn5/S6qGZOL5+g==", "dev": true, "requires": { - "spdx-license-ids": "1.2.2" + "spdx-expression-parse": "3.0.0", + "spdx-license-ids": "3.0.0" } }, - "spdx-expression-parse": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-1.0.4.tgz", - "integrity": "sha1-m98vIOH0DtRH++JzJmGR/O1RYmw=", + "spdx-exceptions": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.1.0.tgz", + "integrity": "sha512-4K1NsmrlCU1JJgUrtgEeTVyfx8VaYea9J9LvARxhbHtVtohPs/gFGG5yy49beySjlIMhhXZ4QqujIZEfS4l6Cg==", "dev": true }, + "spdx-expression-parse": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", + "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", + "dev": true, + "requires": { + "spdx-exceptions": "2.1.0", + "spdx-license-ids": "3.0.0" + } + }, "spdx-license-ids": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz", - "integrity": "sha1-yd96NCRZSt5r0RkA1ZZpbcBrrFc=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.0.tgz", + "integrity": "sha512-2+EPwgbnmOIl8HjGBXXMd9NAu02vLjOO1nWw4kmeRDFyHn+M/ETfHxQUK0oXg8ctgVnl9t3rosNVsZ1jG61nDA==", "dev": true }, + "split": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz", + "integrity": "sha1-zQ7qXmOiEd//frDwkcQTPi0N0o8=", + "requires": { + "through": "2.3.8" + } + }, "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -1853,34 +1874,27 @@ "dev": true }, "sshpk": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.1.tgz", - "integrity": "sha1-US322mKHFEMW3EwY/hzx2UBzm+M=", + "version": "1.14.2", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.2.tgz", + "integrity": "sha1-xvxhZIo9nE52T9P8306hBeSSupg=", "requires": { "asn1": "0.2.3", "assert-plus": "1.0.0", - "bcrypt-pbkdf": "1.0.1", + "bcrypt-pbkdf": "1.0.2", "dashdash": "1.14.1", - "ecc-jsbn": "0.1.1", + "ecc-jsbn": "0.1.2", "getpass": "0.1.7", "jsbn": "0.1.1", + "safer-buffer": "2.1.2", "tweetnacl": "0.14.5" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" - } } }, - "string_decoder": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", - "dev": true, + "stream-combiner": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", + "integrity": "sha1-TV5DPBhSYd3mI8o/RMWGvPXErRQ=", "requires": { - "safe-buffer": "5.1.1" + "duplexer": "0.1.1" } }, "string-width": { @@ -1893,10 +1907,14 @@ "strip-ansi": "4.0.0" } }, - "stringstream": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", - "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=" + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "5.1.2" + } }, "strip-ansi": { "version": "4.0.0", @@ -1928,15 +1946,14 @@ "dev": true }, "strtok3": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-1.3.2.tgz", - "integrity": "sha1-w6jpgrb4+NQVCilvzhUXzcTsJ9E=", + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-1.5.3.tgz", + "integrity": "sha512-Fo6sP59ioxW18D3pFRw0FIzFUEOmVfOpPyMq6IMoA0gAV9n/c1qy18syr5MYW8ee1lvJaQukV5GlnG/JywBAzg==", "requires": { - "@types/es6-promise": "0.0.33", - "es6-promise": "4.1.1", - "fs-extra": "4.0.2", - "then-read-stream": "1.0.3", - "token-types": "0.9.1" + "debug": "3.1.0", + "es6-promise": "4.2.4", + "then-read-stream": "1.2.1", + "token-types": "0.9.4" } }, "supports-color": { @@ -1951,26 +1968,12 @@ "integrity": "sha512-UUkEAPdSGxtRpiV9ozJ5cMTtYiqz7Ni1OGqLXRCynrvzdtR1p+cfOWe2RJLwvUG8hNanaSRjecIqwOjqeatDsA==", "dev": true, "requires": { - "ajv": "5.2.3", - "ajv-keywords": "2.1.0", - "chalk": "2.1.0", - "lodash": "4.17.4", + "ajv": "5.5.2", + "ajv-keywords": "2.1.1", + "chalk": "2.4.1", + "lodash": "4.17.10", "slice-ansi": "1.0.0", "string-width": "2.1.1" - }, - "dependencies": { - "ajv": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.2.3.tgz", - "integrity": "sha1-wG9Zh3jETGsWGrr+NGa4GtGBTtI=", - "dev": true, - "requires": { - "co": "4.6.0", - "fast-deep-equal": "1.0.0", - "json-schema-traverse": "0.3.1", - "json-stable-stringify": "1.0.1" - } - } } }, "text-table": { @@ -1980,15 +1983,17 @@ "dev": true }, "then-read-stream": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/then-read-stream/-/then-read-stream-1.0.3.tgz", - "integrity": "sha1-lwR4ZiqR382+APnxNfF7zPKQcf8=" + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/then-read-stream/-/then-read-stream-1.2.1.tgz", + "integrity": "sha512-jlPrHP7M1NUddX21McA8TL1jVdB0cZ9va5yd452YHiLk42ume0WSub9wvecGy6jNjnl5kMPlfMT54WrwpNoLAA==", + "requires": { + "es6-promise": "4.2.4" + } }, "through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", - "dev": true + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" }, "tmp": { "version": "0.0.33", @@ -2000,30 +2005,24 @@ } }, "token-types": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/token-types/-/token-types-0.9.1.tgz", - "integrity": "sha1-xfm+Wfs7HsfLeAtAglMvkuVp+8Y=" + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/token-types/-/token-types-0.9.4.tgz", + "integrity": "sha512-KSl/Q1GJ4FoxbqKCLhTiIowVzom2cP0fgWGXKsJupbJQqfnCDmxkdMopIrt+y5Ak6YAGdy9TKpplWDioaBsbEw==" }, "tough-cookie": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.2.tgz", - "integrity": "sha1-8IH3bkyFcg5sN6X6ztc3FQ2EByo=", + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz", + "integrity": "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==", "requires": { "punycode": "1.4.1" } }, - "tryit": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tryit/-/tryit-1.0.3.tgz", - "integrity": "sha1-OTvnMKlEb9Hq1tpZoBQwjzbCics=", - "dev": true - }, "tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", "requires": { - "safe-buffer": "5.1.1" + "safe-buffer": "5.1.2" } }, "tweetnacl": { @@ -2048,14 +2047,14 @@ "dev": true }, "underscore": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz", - "integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI=" + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.1.tgz", + "integrity": "sha512-5/4etnCkd9c8gwgowi5/om/mYO5ajCaOgdzj/oW+0eQV9WxKBDZw5+ycmKmeaTXjInS/W0BzpGLo2xR2aBwZdg==" }, "universalify": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.1.tgz", - "integrity": "sha1-+nG63UQ3r0wUiEHjs7Fl+enlkLc=" + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" }, "url": { "version": "0.10.3", @@ -2080,18 +2079,18 @@ "dev": true }, "uuid": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz", - "integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g==" + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" }, "validate-npm-package-license": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz", - "integrity": "sha1-KAS6vnEq0zeUWaz74kdGqywwP7w=", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.3.tgz", + "integrity": "sha512-63ZOUnL4SIXj4L0NixR3L1lcjO38crAbgrTpl28t8jjrfuiOBL5Iygm+60qPs/KsZGzPNg6Smnc/oY16QTjF0g==", "dev": true, "requires": { - "spdx-correct": "1.0.2", - "spdx-expression-parse": "1.0.4" + "spdx-correct": "3.0.0", + "spdx-expression-parse": "3.0.0" } }, "verror": { @@ -2102,13 +2101,6 @@ "assert-plus": "1.0.0", "core-util-is": "1.0.2", "extsprintf": "1.3.0" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" - } } }, "wav-file-info": { @@ -2117,9 +2109,9 @@ "integrity": "sha1-aAp160w0a34/RX55AqexmDUG5N4=" }, "which": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz", - "integrity": "sha512-xcJpopdamTuY5duC/KnTTNBraPK54YwpenP4lzxU8H91GudWpFv38u0CKjclE1Wi2EH2EDz5LRcHcKbCIzqGyg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", "dev": true, "requires": { "isexe": "2.0.0" @@ -2146,29 +2138,33 @@ } }, "xml-flow": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/xml-flow/-/xml-flow-1.0.1.tgz", - "integrity": "sha1-jHfVkY8wciQ8MJV6RsJnlFP+cLs=", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/xml-flow/-/xml-flow-1.0.2.tgz", + "integrity": "sha512-TfV52Su2083n8w61l1hGI5K3ZAkn4hmaon3oJHc+TRQo0QkKLMPAPdMpGMJd+5Aik6ZUicPDjBINO8QxLkZGgg==", "requires": { - "sax": "1.2.1" + "sax": "1.2.4" + }, + "dependencies": { + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + } } }, "xml2js": { - "version": "0.4.17", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.17.tgz", - "integrity": "sha1-F76T6q4/O3eTWceVtBlwWogX6Gg=", + "version": "0.4.19", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", + "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", "requires": { "sax": "1.2.1", - "xmlbuilder": "4.2.1" + "xmlbuilder": "9.0.7" } }, "xmlbuilder": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-4.2.1.tgz", - "integrity": "sha1-qlijBBoGb5DqoWwvU4n/GfP0YaU=", - "requires": { - "lodash": "4.17.4" - } + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", + "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=" }, "yallist": { "version": "2.1.2", diff --git a/package.json b/package.json index 016695d5..e0ab1fec 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sonos-http-api", - "version": "1.6.2", + "version": "1.6.3", "description": "A simple node app for controlling a Sonos system with basic HTTP requests", "scripts": { "start": "node server.js", @@ -18,11 +18,11 @@ "fuse.js": "^2.5.0", "html-entities": "^1.2.1", "json5": "^0.5.1", - "mime": "^1.4.0", + "mime": "^1.4.1", "music-metadata": "^0.8.4", "node-static": "^0.7.9", "request-promise": "~1.0.2", - "sonos-discovery": "https://github.com/jishi/node-sonos-discovery/archive/v1.7.0.tar.gz", + "sonos-discovery": "https://github.com/jishi/node-sonos-discovery/archive/v1.7.1.tar.gz", "wav-file-info": "0.0.8" }, "engines": { From 1b4e7aa4e38ba1bce250ca86d993f0b576ac842a Mon Sep 17 00:00:00 2001 From: Jimmy Shimizu Date: Sun, 28 Oct 2018 11:24:54 +0100 Subject: [PATCH 073/123] fix: Wait for file to close before reading --- lib/tts-providers/microsoft.js | 2 +- package-lock.json | 209 +++++++++++---------------------- package.json | 2 +- 3 files changed, 72 insertions(+), 141 deletions(-) diff --git a/lib/tts-providers/microsoft.js b/lib/tts-providers/microsoft.js index bb5e4923..371434d4 100644 --- a/lib/tts-providers/microsoft.js +++ b/lib/tts-providers/microsoft.js @@ -106,7 +106,7 @@ function microsoft(phrase, voiceName) { const file = fs.createWriteStream(filepath); res.pipe(file); - res.on('end', () => { + file.on('close', () => { resolve(); }) }) diff --git a/package-lock.json b/package-lock.json index 90846171..4130cd4c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -111,14 +111,6 @@ "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" }, - "async": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.1.tgz", - "integrity": "sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==", - "requires": { - "lodash": "4.17.10" - } - }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -426,6 +418,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, "requires": { "ms": "2.0.0" } @@ -465,11 +458,6 @@ "esutils": "2.0.2" } }, - "duplexer": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", - "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=" - }, "ecc-jsbn": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", @@ -489,11 +477,6 @@ "is-arrayish": "0.2.1" } }, - "es6-promise": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.4.tgz", - "integrity": "sha512-/NdNZVJg+uZgtm9eS3O6lrOLYmQag2DjdEXuPaHlZ6RuVqgqaVZfgYCepEIKsLqwdQArOPtC3XzRLqGGfT8KQQ==" - }, "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -704,20 +687,6 @@ "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", "dev": true }, - "event-stream": { - "version": "3.3.4", - "resolved": "http://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", - "integrity": "sha1-SrTJoPWlTbkzi0w02Gv86PSzVXE=", - "requires": { - "duplexer": "0.1.1", - "from": "0.1.7", - "map-stream": "0.1.0", - "pause-stream": "0.0.11", - "split": "0.3.3", - "stream-combiner": "0.0.4", - "through": "2.3.8" - } - }, "events": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", @@ -779,6 +748,11 @@ "object-assign": "4.1.1" } }, + "file-type": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-10.2.0.tgz", + "integrity": "sha512-eqX81S1PWdLDPW39yyB214TVVOsUQjSmPcyUjeVH6ksH+94Y2YA/ItiIwa53rJiSofJZLK6lGsuCE3rwt0vp4w==" + }, "find-up": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", @@ -816,21 +790,6 @@ "mime-types": "2.1.19" } }, - "from": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", - "integrity": "sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=" - }, - "fs-extra": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz", - "integrity": "sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==", - "requires": { - "graceful-fs": "4.1.11", - "jsonfile": "4.0.0", - "universalify": "0.1.2" - } - }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -899,7 +858,8 @@ "graceful-fs": { "version": "4.1.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", - "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=" + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", + "dev": true }, "har-schema": { "version": "2.0.0", @@ -1160,14 +1120,6 @@ "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=" }, - "jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", - "requires": { - "graceful-fs": "4.1.11" - } - }, "jsprim": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", @@ -1222,12 +1174,8 @@ "lodash": { "version": "4.17.10", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", - "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==" - }, - "lodash.assign": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz", - "integrity": "sha1-DZnzzNem0mHRm9rrkkUAXShYCOc=" + "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==", + "dev": true }, "lru-cache": { "version": "4.1.3", @@ -1239,10 +1187,10 @@ "yallist": "2.1.2" } }, - "map-stream": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", - "integrity": "sha1-5WqpTEyAVaFkBKBnS3jyFffI4ZQ=" + "media-typer": { + "version": "0.3.0", + "resolved": "http://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" }, "mime": { "version": "1.6.0", @@ -1268,11 +1216,6 @@ "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", "dev": true }, - "mingo": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/mingo/-/mingo-1.3.3.tgz", - "integrity": "sha1-aSLE0Ufvx3GgFCWixMj3eER4xUY=" - }, "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", @@ -1307,19 +1250,35 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true }, "music-metadata": { - "version": "0.8.8", - "resolved": "https://registry.npmjs.org/music-metadata/-/music-metadata-0.8.8.tgz", - "integrity": "sha512-Su4vm5YKd3u9N/cRz+96aYNTWnzRXrIbu0B+P+zKuWNYOtbbZ6M6DgmjWEmJYW6NjzhJAxesbrGXJ7K57RFUEg==", - "requires": { - "es6-promise": "4.2.4", - "fs-extra": "4.0.3", - "save": "2.3.2", - "strtok3": "1.5.3", - "then-read-stream": "1.2.1", - "token-types": "0.9.4" + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/music-metadata/-/music-metadata-3.1.5.tgz", + "integrity": "sha512-ZR+sy0Bu6iojocbEQ6mH/UTfpD9OCkCNcbbewdycOX4oF8iNEwp0TXtASmAW9Y4xKIcsgWjI/e5yzWfpu+ue1Q==", + "requires": { + "debug": "4.1.0", + "file-type": "10.2.0", + "media-typer": "0.3.0", + "strtok3": "2.1.1", + "then-read-stream": "1.3.1", + "token-types": "1.0.1" + }, + "dependencies": { + "debug": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.0.tgz", + "integrity": "sha512-heNPJUJIqC+xB6ayLAMHaIrmN9HKa7aQO8MGqKpvCA+uJYVcvR6l5kgdrhRuwPFHU7P5/A1w0BjByPHwpfTDKg==", + "requires": { + "ms": "2.1.1" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + } } }, "mute-stream": { @@ -1491,14 +1450,6 @@ "pify": "2.3.0" } }, - "pause-stream": { - "version": "0.0.11", - "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", - "integrity": "sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=", - "requires": { - "through": "2.3.8" - } - }, "performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", @@ -1755,17 +1706,6 @@ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, - "save": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/save/-/save-2.3.2.tgz", - "integrity": "sha1-hZJnS1VlzE4SvG3dnLCfgo4+z30=", - "requires": { - "async": "2.6.1", - "event-stream": "3.3.4", - "lodash.assign": "4.2.0", - "mingo": "1.3.3" - } - }, "sax": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", @@ -1859,14 +1799,6 @@ "integrity": "sha512-2+EPwgbnmOIl8HjGBXXMd9NAu02vLjOO1nWw4kmeRDFyHn+M/ETfHxQUK0oXg8ctgVnl9t3rosNVsZ1jG61nDA==", "dev": true }, - "split": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz", - "integrity": "sha1-zQ7qXmOiEd//frDwkcQTPi0N0o8=", - "requires": { - "through": "2.3.8" - } - }, "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -1889,14 +1821,6 @@ "tweetnacl": "0.14.5" } }, - "stream-combiner": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", - "integrity": "sha1-TV5DPBhSYd3mI8o/RMWGvPXErRQ=", - "requires": { - "duplexer": "0.1.1" - } - }, "string-width": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", @@ -1946,14 +1870,28 @@ "dev": true }, "strtok3": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-1.5.3.tgz", - "integrity": "sha512-Fo6sP59ioxW18D3pFRw0FIzFUEOmVfOpPyMq6IMoA0gAV9n/c1qy18syr5MYW8ee1lvJaQukV5GlnG/JywBAzg==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-2.1.1.tgz", + "integrity": "sha512-fASvEpC2aDaTS6TZHffbW4w8li471ws3qQc0ag1KdftigxvOxhjIF8NWiDLSiWQV8YQSKhZggq8YbGTzvicCHA==", "requires": { - "debug": "3.1.0", - "es6-promise": "4.2.4", - "then-read-stream": "1.2.1", - "token-types": "0.9.4" + "debug": "4.1.0", + "then-read-stream": "1.3.1", + "token-types": "1.0.1" + }, + "dependencies": { + "debug": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.0.tgz", + "integrity": "sha512-heNPJUJIqC+xB6ayLAMHaIrmN9HKa7aQO8MGqKpvCA+uJYVcvR6l5kgdrhRuwPFHU7P5/A1w0BjByPHwpfTDKg==", + "requires": { + "ms": "2.1.1" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + } } }, "supports-color": { @@ -1983,17 +1921,15 @@ "dev": true }, "then-read-stream": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/then-read-stream/-/then-read-stream-1.2.1.tgz", - "integrity": "sha512-jlPrHP7M1NUddX21McA8TL1jVdB0cZ9va5yd452YHiLk42ume0WSub9wvecGy6jNjnl5kMPlfMT54WrwpNoLAA==", - "requires": { - "es6-promise": "4.2.4" - } + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/then-read-stream/-/then-read-stream-1.3.1.tgz", + "integrity": "sha512-RDxUU490df9+DlL0qd1Uc8EpKOwHQodjh5Gd2qZ249k4zYyv8FLgb3g9ygII1EItT35SDOK1LE3vX2zNZKCSXA==" }, "through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true }, "tmp": { "version": "0.0.33", @@ -2005,9 +1941,9 @@ } }, "token-types": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/token-types/-/token-types-0.9.4.tgz", - "integrity": "sha512-KSl/Q1GJ4FoxbqKCLhTiIowVzom2cP0fgWGXKsJupbJQqfnCDmxkdMopIrt+y5Ak6YAGdy9TKpplWDioaBsbEw==" + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/token-types/-/token-types-1.0.1.tgz", + "integrity": "sha512-Q2yPT4GA4gmIAgUrL9O9LRzKFKJ8FA1P5eiOfC2eH+IZMSJVQBCML1SOooeRicIIvTr/UZuTAX5PqMzgBXJOvg==" }, "tough-cookie": { "version": "2.3.4", @@ -2051,11 +1987,6 @@ "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.1.tgz", "integrity": "sha512-5/4etnCkd9c8gwgowi5/om/mYO5ajCaOgdzj/oW+0eQV9WxKBDZw5+ycmKmeaTXjInS/W0BzpGLo2xR2aBwZdg==" }, - "universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" - }, "url": { "version": "0.10.3", "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", diff --git a/package.json b/package.json index e0ab1fec..b7ec6dd5 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "html-entities": "^1.2.1", "json5": "^0.5.1", "mime": "^1.4.1", - "music-metadata": "^0.8.4", + "music-metadata": "^3.1.5", "node-static": "^0.7.9", "request-promise": "~1.0.2", "sonos-discovery": "https://github.com/jishi/node-sonos-discovery/archive/v1.7.1.tar.gz", From 5400e021006876de87cf046eb7061ef6fe4a4cb0 Mon Sep 17 00:00:00 2001 From: Jimmy Shimizu Date: Sun, 28 Oct 2018 11:46:36 +0100 Subject: [PATCH 074/123] fix: Add support for more microsoft languages --- README.md | 4 ++-- lib/tts-providers/microsoft.js | 13 +++++++++++++ package.json | 2 +- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 308ee1b1..a855cd8a 100644 --- a/README.md +++ b/README.md @@ -500,10 +500,10 @@ Example: Supported voices are: -Hoda, Hedda, Stefan, Catherine, Linda, Susan, George, Ravi, ZiraRUS, BenjaminRUS, Laura, Pablo, Raul, Caroline, Julie, Paul, Cosimo, Ayumi, Ichiro, Daniel, Irina, Pavel, HuihuiRUS, Yaoyao, Kangkang, Tracy, Danny, Yating, Zhiwei + Hoda, Naayf, Ivan, HerenaRUS, Jakub, Vit, HelleRUS, Michael, Karsten, Hedda, Stefan, Catherine, Linda, Susan, George, Ravi, ZiraRUS, BenjaminRUS, Laura, Pablo, Raul, Caroline, Julie, Paul, Cosimo, Ayumi, Ichiro, Daniel, Irina, Pavel, HuihuiRUS, Yaoyao, Kangkang, Tracy, Danny, Yating, Zhiwei See https://www.microsoft.com/cognitive-services/en-us/speech-api/documentation/API-Reference-REST/BingVoiceOutput#SupLocales to identify -which language and gender it maps against. +which language and gender it maps against. If your desired voice is not in the list of supported one, raise an issue about adding it or send me a PR. #### AWS Polly diff --git a/lib/tts-providers/microsoft.js b/lib/tts-providers/microsoft.js index 371434d4..b0789d5c 100644 --- a/lib/tts-providers/microsoft.js +++ b/lib/tts-providers/microsoft.js @@ -130,6 +130,14 @@ function microsoft(phrase, voiceName) { const VOICE = { Hoda: { language: 'ar-EG', gender: 'Female', font: 'Microsoft Server Speech Text to Speech Voice (ar-EG, Hoda)' }, + Naayf: { language: 'ar-SA', gender: 'Male', font: 'Microsoft Server Speech Text to Speech Voice (ar-SA, Naayf)' }, + Ivan: { language: 'bg-BG', gender: 'Male', font: 'Microsoft Server Speech Text to Speech Voice (bg-BG, Ivan)' }, + HerenaRUS: { language: 'ca-ES', gender: 'Female', font: 'Microsoft Server Speech Text to Speech Voice (ca-ES, HerenaRUS)' }, + Jakub: { language: 'cs-CZ', gender: 'Male', font: 'Microsoft Server Speech Text to Speech Voice (cs-CZ, Jakub)' }, + Vit: { language: 'cs-CZ', gender: 'Male', font: 'Microsoft Server Speech Text to Speech Voice (cs-CZ, Vit)' }, + HelleRUS: { language: 'da-DK', gender: 'Female', font: 'Microsoft Server Speech Text to Speech Voice (da-DK, HelleRUS)' }, + Michael: { language: 'de-AT', gender: 'Male', font: 'Microsoft Server Speech Text to Speech Voice (de-AT, Michael)' }, + Karsten: { language: 'de-CH', gender: 'Male', font: 'Microsoft Server Speech Text to Speech Voice (de-CH, Karsten)' }, Hedda: { language: 'de-DE', gender: 'Female', font: 'Microsoft Server Speech Text to Speech Voice (de-DE, Hedda)' }, Stefan: { language: 'de-DE', @@ -217,6 +225,11 @@ const VOICE = { gender: 'Male', font: 'Microsoft Server Speech Text to Speech Voice (pt-BR, Daniel, Apollo)' }, + Andrei: { + language: 'ro-RO', + gender: 'Male', + font: 'Microsoft Server Speech Text to Speech Voice (ro-RO, Andrei)', + }, Irina: { language: 'ru-RU', gender: 'Female', diff --git a/package.json b/package.json index b7ec6dd5..4cc634f9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sonos-http-api", - "version": "1.6.3", + "version": "1.6.4", "description": "A simple node app for controlling a Sonos system with basic HTTP requests", "scripts": { "start": "node server.js", From 2fd4b248079bf2f9e5cad6bc3838d16b0edda13b Mon Sep 17 00:00:00 2001 From: Connor Knabe Date: Sun, 28 Oct 2018 05:53:15 -0500 Subject: [PATCH 075/123] Adding another project build using this API (#654) Personally, I am a big fan of echo-sonos so I figured it'd be nice to have it listed here --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index a855cd8a..c885d5ee 100644 --- a/README.md +++ b/README.md @@ -1026,6 +1026,11 @@ If port 3500 is occupied while trying to bind it, it will try using 3501, 3502, Amazon Alexa voice layer on top of the amazing NodeJS component https://github.com/hypermoose/AlexaForSonos +**Echo Sonos (Alexa Skills)** + +Amazon Echo integration with Sonos +https://github.com/rgraciano/echo-sonos + **JukeBot (Ruby)** A Slack bot that can control a Sonos instance. Custom spotify integration to find music. From d597e06ce580dff2ae3354b29fc1496c1741daeb Mon Sep 17 00:00:00 2001 From: Matthew Reishus Date: Sun, 28 Oct 2018 05:53:40 -0500 Subject: [PATCH 076/123] Fix poss. crash on //musicsearch/spotify/song/ related to available_markets (#647) When I used 'http://localhost:5005/Office/musicsearch/spotify/song/red+hot+chili+peppers' straight from the docs, it was crashing because there was no "track.available_markets" data. This may be because I specifed a non-commercial app when I generated my spotify clientId + secret? Anyway, this forgoes the available_markets checking if the data is not there. --- lib/music_services/spotifyDef.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/music_services/spotifyDef.js b/lib/music_services/spotifyDef.js index bcb6df1f..d20b7216 100644 --- a/lib/music_services/spotifyDef.js +++ b/lib/music_services/spotifyDef.js @@ -226,7 +226,7 @@ function loadTracks(type, tracksJson) if (tracksJson.tracks.items.length > 0) { // Filtered list of tracks to play tracks.queueTracks = tracksJson.tracks.items.reduce(function(tracksArray, track) { - if (track.available_markets.indexOf(country) != -1) { + if (track.available_markets == null || track.available_markets.indexOf(country) != -1) { var skip = false; for (var j=0; (j < tracksArray.length) && !skip ; j++) { From 60d4f8ca167520f29192c5f3df083fa944ce6d34 Mon Sep 17 00:00:00 2001 From: markdstjohn Date: Sun, 28 Oct 2018 03:58:25 -0700 Subject: [PATCH 077/123] Fix Sirius XM (#620) * Remove account id from siriusXM.js, since it isn't necessary * Fix potential crash if handleAction is not thenable --- lib/actions/siriusXM.js | 50 ++++++++++------------------------------- lib/sonos-http-api.js | 2 +- 2 files changed, 13 insertions(+), 39 deletions(-) diff --git a/lib/actions/siriusXM.js b/lib/actions/siriusXM.js index 8c77d23e..0930ec67 100644 --- a/lib/actions/siriusXM.js +++ b/lib/actions/siriusXM.js @@ -3,13 +3,11 @@ const request = require('request-promise'); const Fuse = require('fuse.js'); const channels = require('../sirius-channels.json'); -var accountId = ''; - -function getSiriusXmMetadata(id, parent, title, auth) { +function getSiriusXmMetadata(id, parent, title) { return ` ${title}object.item.audioItem.audioBroadcast - SA_RINCON9479_${auth}`; + _`; } function getSiriusXmUri(id) { @@ -27,24 +25,6 @@ function adjustStation(name) { return name; } -function getAccountId(player) -{ - accountId = ''; - - return request({url: player.baseUrl + '/status/accounts',json: false}) - .then((res) => { - var actLoc = res.indexOf('Account Type="9479"'); - - if (actLoc != -1) { - var idLoc = res.indexOf('', actLoc)+4; - - accountId = res.substring(idLoc,res.indexOf('',idLoc)); - } - - return Promise.resolve(); - }); -} - function siriusXM(player, values) { var auth = ''; var results = []; @@ -86,24 +66,18 @@ function siriusXM(player, values) { return Promise.resolve("success"); } else { // Play the specified SiriusXM channel or station + var searchVal = values[0]; + var fuzzy = new Fuse(channels, { keys: ["channelNum", "title"] }); - return getAccountId(player) - .then(() => { - if (accountId != '') { - var searchVal = values[0]; - var fuzzy = new Fuse(channels, { keys: ["channelNum", "title"] }); + results = fuzzy.search(searchVal); + if (results.length > 0) { + const channel = results[0]; + const uri = getSiriusXmUri(channel.id); + const metadata = getSiriusXmMetadata(channel.id, channel.parentID, channel.fullTitle); - results = fuzzy.search(searchVal); - if (results.length > 0) { - const channel = results[0]; - const uri = getSiriusXmUri(channel.id); - const metadata = getSiriusXmMetadata(channel.id, channel.parentID, channel.fullTitle, accountId); - - return player.coordinator.setAVTransport(uri, metadata) - .then(() => player.coordinator.play()); - } - } - }); + return player.coordinator.setAVTransport(uri, metadata) + .then(() => player.coordinator.play()); + } } } diff --git a/lib/sonos-http-api.js b/lib/sonos-http-api.js index 460bbf8a..3b11c8a4 100644 --- a/lib/sonos-http-api.js +++ b/lib/sonos-http-api.js @@ -91,7 +91,7 @@ function HttpAPI(discovery, settings) { } opt.player = player; - handleAction(opt) + Promise.resolve(handleAction(opt)) .then((response) => { if (!response || response.constructor.name === 'IncomingMessage') { response = { status: 'success' }; From d9fdf1865075d88e5678f060165ffbad0454a72b Mon Sep 17 00:00:00 2001 From: Jimmy Shimizu Date: Thu, 1 Nov 2018 18:11:13 +0100 Subject: [PATCH 078/123] fix: Use album art from enqueuedTransportURIMetadata Because sonos-discovery 1.7.2 --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 4cc634f9..f3771bfa 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sonos-http-api", - "version": "1.6.4", + "version": "1.6.5", "description": "A simple node app for controlling a Sonos system with basic HTTP requests", "scripts": { "start": "node server.js", @@ -22,7 +22,7 @@ "music-metadata": "^3.1.5", "node-static": "^0.7.9", "request-promise": "~1.0.2", - "sonos-discovery": "https://github.com/jishi/node-sonos-discovery/archive/v1.7.1.tar.gz", + "sonos-discovery": "https://github.com/jishi/node-sonos-discovery/archive/v1.7.2.tar.gz", "wav-file-info": "0.0.8" }, "engines": { From 9ab8c6e0d02c71f609297a557e306fc64e6bea07 Mon Sep 17 00:00:00 2001 From: Jimmy Shimizu Date: Sat, 1 Dec 2018 12:41:14 +0100 Subject: [PATCH 079/123] fix: Switch to queue playback if needed --- lib/actions/amazonMusic.js | 7 ++++++- lib/actions/appleMusic.js | 6 +++++- package.json | 2 +- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/lib/actions/amazonMusic.js b/lib/actions/amazonMusic.js index dcd84661..35207364 100644 --- a/lib/actions/amazonMusic.js +++ b/lib/actions/amazonMusic.js @@ -60,7 +60,12 @@ function amazonMusic(player, values) { return player.coordinator.addURIToQueue(uri, metadata); } else if (action == 'now') { nextTrackNo = player.coordinator.state.trackNo + 1; - return player.coordinator.addURIToQueue(uri, metadata, true, nextTrackNo) + let promise = Promise.resolve(); + if (player.coordinator.avTransportUri.startsWith('x-rincon-queue') === false) { + promise = promise.then(() => player.coordinator.setAVTransport(`x-rincon-queue:${player.coordinator.uuid}#0`)); + } + + return promise.then(() => player.coordinator.addURIToQueue(uri, metadata, true, nextTrackNo)) .then(() => { if (nextTrackNo != 1) player.coordinator.nextTrack() }) .then(() => player.coordinator.play()); } else if (action == 'next') { diff --git a/lib/actions/appleMusic.js b/lib/actions/appleMusic.js index bb5690ac..7099aa8e 100644 --- a/lib/actions/appleMusic.js +++ b/lib/actions/appleMusic.js @@ -48,7 +48,11 @@ function appleMusic(player, values) { return player.coordinator.addURIToQueue(uri, metadata); } else if (action == 'now') { nextTrackNo = player.coordinator.state.trackNo + 1; - return player.coordinator.addURIToQueue(uri, metadata, true, nextTrackNo) + let promise = Promise.resolve(); + if (player.coordinator.avTransportUri.startsWith('x-rincon-queue') === false) { + promise = promise.then(() => player.coordinator.setAVTransport(`x-rincon-queue:${player.coordinator.uuid}#0`)); + } + return promise.then(() => player.coordinator.addURIToQueue(uri, metadata, true, nextTrackNo)) .then(() => { if (nextTrackNo != 1) player.coordinator.nextTrack() }) .then(() => player.coordinator.play()); } else if (action == 'next') { diff --git a/package.json b/package.json index f3771bfa..ad9c5bb8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sonos-http-api", - "version": "1.6.5", + "version": "1.6.6", "description": "A simple node app for controlling a Sonos system with basic HTTP requests", "scripts": { "start": "node server.js", From e99a83ba92f1cd46af459cda46b4fa171e92d1ed Mon Sep 17 00:00:00 2001 From: Jimmy Shimizu Date: Thu, 17 Jan 2019 13:41:04 +0100 Subject: [PATCH 080/123] fix: Downgrade music-metadata for node 4 compatibility chore: Add debug endpoint chore: improve test_endpoint output Fixes #672, #669 --- lib/actions/debug.js | 29 +++++++++++++++++++++++++++++ package.json | 4 ++-- test_endpoint.js | 9 ++++++--- 3 files changed, 37 insertions(+), 5 deletions(-) create mode 100644 lib/actions/debug.js diff --git a/lib/actions/debug.js b/lib/actions/debug.js new file mode 100644 index 00000000..f60f3e06 --- /dev/null +++ b/lib/actions/debug.js @@ -0,0 +1,29 @@ +'use strict'; +const pkg = require('../../package.json'); + +function debug(player) { + const system = player.system; + const debugInfo = { + version: pkg.version, + system: { + localEndpoint: system.localEndpoint, + availableServices: system.availableServices, + }, + players: system.players.map(x => ({ + roomName: x.roomName, + uuid: x.uuid, + coordinator: x.coordinator.uuid, + avTransportUri: x.avTransportUri, + avTransportUriMetadata: x.avTransportUriMetadata, + enqueuedTransportUri: x.enqueuedTransportUri, + enqueuedTransportUriMetadata: x.enqueuedTransportUriMetadata, + baseUrl: x.baseUrl, + state: x._state + })) + }; + return Promise.resolve(debugInfo); +} + +module.exports = function (api) { + api.registerAction('debug', debug); +} diff --git a/package.json b/package.json index ad9c5bb8..55384cbc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sonos-http-api", - "version": "1.6.6", + "version": "1.6.7", "description": "A simple node app for controlling a Sonos system with basic HTTP requests", "scripts": { "start": "node server.js", @@ -19,7 +19,7 @@ "html-entities": "^1.2.1", "json5": "^0.5.1", "mime": "^1.4.1", - "music-metadata": "^3.1.5", + "music-metadata": "^1.1.0", "node-static": "^0.7.9", "request-promise": "~1.0.2", "sonos-discovery": "https://github.com/jishi/node-sonos-discovery/archive/v1.7.2.tar.gz", diff --git a/test_endpoint.js b/test_endpoint.js index 2fd72545..eedab434 100644 --- a/test_endpoint.js +++ b/test_endpoint.js @@ -2,6 +2,7 @@ const http = require('http'); let server = http.createServer((req, res) => { + console.log(req.method, req.url); for (let header in req.headers) { console.log(header + ':', req.headers[header]); } @@ -14,9 +15,11 @@ let server = http.createServer((req, res) => { req.on('end', () => { res.end(); - const json = JSON.parse(buffer.join('')); - console.dir(json, {depth: 10}); - console.log(''); + try { + const json = JSON.parse(buffer.join('')); + console.dir(json, { depth: 10 }); + console.log(''); + } catch (e) {} }); }); From 7fc74e8f46c341f2458626b3ab392ea671255db8 Mon Sep 17 00:00:00 2001 From: JayVee2 <35402108+JayVee2@users.noreply.github.com> Date: Wed, 27 Mar 2019 18:58:53 +0100 Subject: [PATCH 081/123] Force creation of MP3 instead of WAV file to play with Sonos https://github.com/jishi/node-sonos-http-api/issues/686 --- lib/tts-providers/voicerss.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/tts-providers/voicerss.js b/lib/tts-providers/voicerss.js index ea5aa24d..6b677dde 100644 --- a/lib/tts-providers/voicerss.js +++ b/lib/tts-providers/voicerss.js @@ -16,7 +16,8 @@ function voicerss(phrase, language) { language = 'en-gb'; } // Use voicerss tts translation service to create a mp3 file - const ttsRequestUrl = `http://api.voicerss.org/?key=${settings.voicerss}&f=22khz_16bit_mono&hl=${language}&src=${encodeURIComponent(phrase)}`; + // Option "c=MP3" added. Otherwise a WAV file is created that won't play on Sonos. + const ttsRequestUrl = `http://api.voicerss.org/?key=${settings.voicerss}&f=22khz_16bit_mono&hl=${language}&src=${encodeURIComponent(phrase)}&c=MP3`; // Construct a filesystem neutral filename const phraseHash = crypto.createHash('sha1').update(phrase).digest('hex'); From b7bc6b45039961ba3a563d5f2396272ad544fd78 Mon Sep 17 00:00:00 2001 From: Jimmy Shimizu Date: Thu, 28 Mar 2019 14:32:57 +0100 Subject: [PATCH 082/123] fix: Use strict --- lib/presets-loader.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presets-loader.js b/lib/presets-loader.js index 986cea4a..ef8312d4 100644 --- a/lib/presets-loader.js +++ b/lib/presets-loader.js @@ -1,4 +1,4 @@ -"use strict"; +'use strict'; const fs = require('fs'); const util = require('util'); const path = require('path'); From 87fd1537ff4fc12b6b1e59795985e12efea25d6b Mon Sep 17 00:00:00 2001 From: Jimmy Shimizu Date: Thu, 28 Mar 2019 14:44:19 +0100 Subject: [PATCH 083/123] chore: Patch version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 55384cbc..6ff067d8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sonos-http-api", - "version": "1.6.7", + "version": "1.6.8", "description": "A simple node app for controlling a Sonos system with basic HTTP requests", "scripts": { "start": "node server.js", From 921e49ba8b72b6b3b4ba29a79f14b6e999947563 Mon Sep 17 00:00:00 2001 From: Jimmy Shimizu Date: Thu, 28 Mar 2019 14:59:13 +0100 Subject: [PATCH 084/123] fix: Add JessaNeural #679 --- lib/tts-providers/microsoft.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/tts-providers/microsoft.js b/lib/tts-providers/microsoft.js index b0789d5c..e24803ee 100644 --- a/lib/tts-providers/microsoft.js +++ b/lib/tts-providers/microsoft.js @@ -274,6 +274,11 @@ const VOICE = { language: 'zh-TW', gender: 'Male', font: 'Microsoft Server Speech Text to Speech Voice (zh-TW, Zhiwei, Apollo)' + }, + JessaNeural: { + language: 'en-US', + gender: 'Female', + font: 'Microsoft Server Speech Text to Speech Voice (en-US, JessaNeural)' } }; From a1e93825ac40c157d6931e6fad9c78ef51f8b348 Mon Sep 17 00:00:00 2001 From: Stefan Kienzle Date: Mon, 17 Jun 2019 17:56:16 +0200 Subject: [PATCH 085/123] Add Napster/Aldi Life Music support (#699) * Add Napster/Aldi Life Music support * Bugfix Napster "now" while radio station is played * Final fixes for Napster and Aldi Life Music (powered by Napster) --- lib/actions/aldilifeMusic.js | 65 ++++++++++++++++++++++++++++++++++++ lib/actions/napster.js | 65 ++++++++++++++++++++++++++++++++++++ 2 files changed, 130 insertions(+) create mode 100644 lib/actions/aldilifeMusic.js create mode 100644 lib/actions/napster.js diff --git a/lib/actions/aldilifeMusic.js b/lib/actions/aldilifeMusic.js new file mode 100644 index 00000000..570a7285 --- /dev/null +++ b/lib/actions/aldilifeMusic.js @@ -0,0 +1,65 @@ +'use strict'; +function getMetadata(id, parentUri, type, title) { + return ` + "${title}"${type} + SA_RINCON55303_X_#Svc55303-0-Token`; +} + +function getUri(id, type) { + var uri = { + song: `x-sonos-http:ondemand_track%3a%3atra.${id}%7cv1%7cALBUM%7calb.mp4?sid=216&flags=8224&sn=13`, + album: `x-rincon-cpcontainer:100420ecexplore%3aalbum%3a%3aAlb.${id}` + }; + + return uri[type]; +} + +const CLASSES = { + song: 'object.item.audioItem.musicTrack', + album: 'object.container.album.musicAlbum' +}; + +const METADATA_URI_STARTERS = { + song: '10032020ondemand_track%3a%3atra.', + album: '100420ec' +}; + +const PARENTS = { + song: '100420ecexplore%3a', + album: '100420ecexplore%3aalbum%3a' +}; + +function aldilifeMusic(player, values) { + const action = values[0]; + const trackID = values[1].split(':')[1]; + const type = values[1].split(':')[0]; + var nextTrackNo = 0; + + const metadataID = METADATA_URI_STARTERS[type] + encodeURIComponent(trackID); + const metadata = getMetadata(metadataID, PARENTS[type], CLASSES[type], ''); + const uri = getUri(encodeURIComponent(trackID), type); + + if (action == 'queue') { + return player.coordinator.addURIToQueue(uri, metadata); + } else if (action == 'now') { + nextTrackNo = player.coordinator.state.trackNo + 1; + let promise = Promise.resolve(); + if (player.coordinator.avTransportUri.startsWith('x-rincon-queue') === false) { + promise = promise.then(() => player.coordinator.setAVTransport(`x-rincon-queue:${player.coordinator.uuid}#0`)); + } + + return promise.then(() => { + return player.coordinator.addURIToQueue(uri, metadata, true, nextTrackNo) + .then((addToQueueStatus) => player.coordinator.trackSeek(addToQueueStatus.firsttracknumberenqueued)) + .then(() => player.coordinator.play()); + }); + } else if (action == 'next') { + nextTrackNo = player.coordinator.state.trackNo + 1; + return player.coordinator.addURIToQueue(uri, metadata, true, nextTrackNo); + } +} + +module.exports = function (api) { + api.registerAction('aldilifemusic', aldilifeMusic); +}; diff --git a/lib/actions/napster.js b/lib/actions/napster.js new file mode 100644 index 00000000..7540800b --- /dev/null +++ b/lib/actions/napster.js @@ -0,0 +1,65 @@ +'use strict'; +function getMetadata(id, parentUri, type, title) { + return ` + "${title}"${type} + SA_RINCON51975_X_#Svc51975-0-Token`; +} + +function getUri(id, type) { + var uri = { + song: `x-sonos-http:ondemand_track%3a%3atra.${id}%7cv1%7cALBUM%7calb.mp4?sid=203&flags=8224&sn=13`, + album: `x-rincon-cpcontainer:100420ecexplore%3aalbum%3a%3aAlb.${id}` + }; + + return uri[type]; +} + +const CLASSES = { + song: 'object.item.audioItem.musicTrack', + album: 'object.container.album.musicAlbum' +}; + +const METADATA_URI_STARTERS = { + song: '10032020ondemand_track%3a%3atra.', + album: '100420ec' +}; + +const PARENTS = { + song: '100420ecexplore%3a', + album: '100420ecexplore%3aalbum%3a' +}; + +function napster(player, values) { + const action = values[0]; + const trackID = values[1].split(':')[1]; + const type = values[1].split(':')[0]; + var nextTrackNo = 0; + + const metadataID = METADATA_URI_STARTERS[type] + encodeURIComponent(trackID); + const metadata = getMetadata(metadataID, PARENTS[type], CLASSES[type], ''); + const uri = getUri(encodeURIComponent(trackID), type); + + if (action == 'queue') { + return player.coordinator.addURIToQueue(uri, metadata); + } else if (action == 'now') { + nextTrackNo = player.coordinator.state.trackNo + 1; + let promise = Promise.resolve(); + if (player.coordinator.avTransportUri.startsWith('x-rincon-queue') === false) { + promise = promise.then(() => player.coordinator.setAVTransport(`x-rincon-queue:${player.coordinator.uuid}#0`)); + } + + return promise.then(() => { + return player.coordinator.addURIToQueue(uri, metadata, true, nextTrackNo) + .then((addToQueueStatus) => player.coordinator.trackSeek(addToQueueStatus.firsttracknumberenqueued)) + .then(() => player.coordinator.play()); + }); + } else if (action == 'next') { + nextTrackNo = player.coordinator.state.trackNo + 1; + return player.coordinator.addURIToQueue(uri, metadata, true, nextTrackNo); + } +} + +module.exports = function (api) { + api.registerAction('napster', napster); +}; From 49c48a2eb2b69affaad8c3fe09a552715a13a74c Mon Sep 17 00:00:00 2001 From: Jimmy Shimizu Date: Fri, 5 Jul 2019 16:40:07 +0200 Subject: [PATCH 086/123] fix: Upgrade sonos-discovery dep to mitigate `TypeError: Cannot read property 'length' of undefined` Fixes #706 --- package-lock.json | 698 +++++++++++++++++++++++----------------------- package.json | 4 +- 2 files changed, 351 insertions(+), 351 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4130cd4c..8873d5e6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "sonos-http-api", - "version": "1.6.3", + "version": "1.6.9", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -16,7 +16,7 @@ "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", "dev": true, "requires": { - "acorn": "3.3.0" + "acorn": "^3.0.4" }, "dependencies": { "acorn": { @@ -32,10 +32,10 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", "requires": { - "co": "4.6.0", - "fast-deep-equal": "1.1.0", - "fast-json-stable-stringify": "2.0.0", - "json-schema-traverse": "0.3.1" + "co": "^4.6.0", + "fast-deep-equal": "^1.0.0", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.3.0" } }, "ajv-keywords": { @@ -49,8 +49,8 @@ "resolved": "https://registry.npmjs.org/anesidora/-/anesidora-1.2.1.tgz", "integrity": "sha1-/ZWdrLiPx6im5xE+OA8/rQmTipo=", "requires": { - "request": "2.87.0", - "underscore": "1.9.1" + "request": "^2.64.0", + "underscore": "^1.8.3" } }, "ansi-escapes": { @@ -77,7 +77,7 @@ "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, "requires": { - "sprintf-js": "1.0.3" + "sprintf-js": "~1.0.2" } }, "array-union": { @@ -86,7 +86,7 @@ "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", "dev": true, "requires": { - "array-uniq": "1.0.3" + "array-uniq": "^1.0.1" } }, "array-uniq": { @@ -155,9 +155,9 @@ "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", "dev": true, "requires": { - "chalk": "1.1.3", - "esutils": "2.0.2", - "js-tokens": "3.0.2" + "chalk": "^1.1.3", + "esutils": "^2.0.2", + "js-tokens": "^3.0.2" }, "dependencies": { "chalk": { @@ -166,11 +166,11 @@ "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, "requires": { - "ansi-styles": "2.2.1", - "escape-string-regexp": "1.0.5", - "has-ansi": "2.0.0", - "strip-ansi": "3.0.1", - "supports-color": "2.0.0" + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" } }, "strip-ansi": { @@ -179,7 +179,7 @@ "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { - "ansi-regex": "2.1.1" + "ansi-regex": "^2.0.0" } } } @@ -206,7 +206,7 @@ "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", "optional": true, "requires": { - "tweetnacl": "0.14.5" + "tweetnacl": "^0.14.3" } }, "bluebird": { @@ -220,7 +220,7 @@ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, "requires": { - "balanced-match": "1.0.0", + "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, @@ -229,9 +229,9 @@ "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", "requires": { - "base64-js": "1.3.0", - "ieee754": "1.1.8", - "isarray": "1.0.0" + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" } }, "buffer-from": { @@ -252,7 +252,7 @@ "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", "dev": true, "requires": { - "callsites": "0.2.0" + "callsites": "^0.2.0" } }, "callsites": { @@ -272,9 +272,9 @@ "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", "dev": true, "requires": { - "ansi-styles": "3.2.1", - "escape-string-regexp": "1.0.5", - "supports-color": "5.4.0" + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" }, "dependencies": { "ansi-styles": { @@ -283,7 +283,7 @@ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "color-convert": "1.9.2" + "color-convert": "^1.9.0" } }, "supports-color": { @@ -292,7 +292,7 @@ "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", "dev": true, "requires": { - "has-flag": "3.0.0" + "has-flag": "^3.0.0" } } } @@ -315,7 +315,7 @@ "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", "dev": true, "requires": { - "restore-cursor": "2.0.0" + "restore-cursor": "^2.0.0" } }, "cli-width": { @@ -329,8 +329,8 @@ "resolved": "https://registry.npmjs.org/cls-bluebird/-/cls-bluebird-1.1.3.tgz", "integrity": "sha1-syY8EaCJsDlhhaG3q5BNkPAq1Cg=", "requires": { - "is-bluebird": "1.0.2", - "shimmer": "1.2.0" + "is-bluebird": "^1.0.1", + "shimmer": "^1.1.0" } }, "co": { @@ -363,7 +363,7 @@ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", "requires": { - "delayed-stream": "1.0.0" + "delayed-stream": "~1.0.0" } }, "concat-map": { @@ -378,10 +378,10 @@ "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", "dev": true, "requires": { - "buffer-from": "1.1.1", - "inherits": "2.0.3", - "readable-stream": "2.3.6", - "typedarray": "0.0.6" + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" } }, "contains-path": { @@ -401,9 +401,9 @@ "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", "dev": true, "requires": { - "lru-cache": "4.1.3", - "shebang-command": "1.2.0", - "which": "1.3.1" + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" } }, "dashdash": { @@ -411,14 +411,13 @@ "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", "requires": { - "assert-plus": "1.0.0" + "assert-plus": "^1.0.0" } }, "debug": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, "requires": { "ms": "2.0.0" } @@ -435,13 +434,13 @@ "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", "dev": true, "requires": { - "globby": "5.0.0", - "is-path-cwd": "1.0.0", - "is-path-in-cwd": "1.0.1", - "object-assign": "4.1.1", - "pify": "2.3.0", - "pinkie-promise": "2.0.1", - "rimraf": "2.6.2" + "globby": "^5.0.0", + "is-path-cwd": "^1.0.0", + "is-path-in-cwd": "^1.0.0", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "rimraf": "^2.2.8" } }, "delayed-stream": { @@ -455,7 +454,7 @@ "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", "dev": true, "requires": { - "esutils": "2.0.2" + "esutils": "^2.0.2" } }, "ecc-jsbn": { @@ -464,8 +463,8 @@ "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", "optional": true, "requires": { - "jsbn": "0.1.1", - "safer-buffer": "2.1.2" + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" } }, "error-ex": { @@ -474,9 +473,14 @@ "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", "dev": true, "requires": { - "is-arrayish": "0.2.1" + "is-arrayish": "^0.2.1" } }, + "es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" + }, "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -489,44 +493,44 @@ "integrity": "sha512-bT3/1x1EbZB7phzYu7vCr1v3ONuzDtX8WjuM9c0iYxe+cq+pwcKEoQjl7zd3RpC6YOLgnSy3cTN58M2jcoPDIQ==", "dev": true, "requires": { - "ajv": "5.5.2", - "babel-code-frame": "6.26.0", - "chalk": "2.4.1", - "concat-stream": "1.6.2", - "cross-spawn": "5.1.0", - "debug": "3.1.0", - "doctrine": "2.1.0", - "eslint-scope": "3.7.3", - "eslint-visitor-keys": "1.0.0", - "espree": "3.5.4", - "esquery": "1.0.1", - "esutils": "2.0.2", - "file-entry-cache": "2.0.0", - "functional-red-black-tree": "1.0.1", - "glob": "7.1.2", - "globals": "11.7.0", - "ignore": "3.3.10", - "imurmurhash": "0.1.4", - "inquirer": "3.3.0", - "is-resolvable": "1.1.0", - "js-yaml": "3.12.0", - "json-stable-stringify-without-jsonify": "1.0.1", - "levn": "0.3.0", - "lodash": "4.17.10", - "minimatch": "3.0.4", - "mkdirp": "0.5.1", - "natural-compare": "1.4.0", - "optionator": "0.8.2", - "path-is-inside": "1.0.2", - "pluralize": "7.0.0", - "progress": "2.0.0", - "regexpp": "1.1.0", - "require-uncached": "1.0.3", - "semver": "5.5.0", - "strip-ansi": "4.0.0", - "strip-json-comments": "2.0.1", + "ajv": "^5.3.0", + "babel-code-frame": "^6.22.0", + "chalk": "^2.1.0", + "concat-stream": "^1.6.0", + "cross-spawn": "^5.1.0", + "debug": "^3.1.0", + "doctrine": "^2.1.0", + "eslint-scope": "^3.7.1", + "eslint-visitor-keys": "^1.0.0", + "espree": "^3.5.4", + "esquery": "^1.0.0", + "esutils": "^2.0.2", + "file-entry-cache": "^2.0.0", + "functional-red-black-tree": "^1.0.1", + "glob": "^7.1.2", + "globals": "^11.0.1", + "ignore": "^3.3.3", + "imurmurhash": "^0.1.4", + "inquirer": "^3.0.6", + "is-resolvable": "^1.0.0", + "js-yaml": "^3.9.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.3.0", + "lodash": "^4.17.4", + "minimatch": "^3.0.2", + "mkdirp": "^0.5.1", + "natural-compare": "^1.4.0", + "optionator": "^0.8.2", + "path-is-inside": "^1.0.2", + "pluralize": "^7.0.0", + "progress": "^2.0.0", + "regexpp": "^1.0.1", + "require-uncached": "^1.0.3", + "semver": "^5.3.0", + "strip-ansi": "^4.0.0", + "strip-json-comments": "~2.0.1", "table": "4.0.2", - "text-table": "0.2.0" + "text-table": "~0.2.0" } }, "eslint-config-airbnb-base": { @@ -535,7 +539,7 @@ "integrity": "sha512-/vjm0Px5ZCpmJqnjIzcFb9TKZrKWz0gnuG/7Gfkt0Db1ELJR51xkZth+t14rYdqWgX836XbuxtArbIHlVhbLBA==", "dev": true, "requires": { - "eslint-restricted-globals": "0.1.1" + "eslint-restricted-globals": "^0.1.1" } }, "eslint-import-resolver-node": { @@ -544,8 +548,8 @@ "integrity": "sha512-sfmTqJfPSizWu4aymbPr4Iidp5yKm8yDkHp+Ir3YiTHiiDfxh69mOUsmiqW6RZ9zRXFaF64GtYmN7e+8GHBv6Q==", "dev": true, "requires": { - "debug": "2.6.9", - "resolve": "1.8.1" + "debug": "^2.6.9", + "resolve": "^1.5.0" }, "dependencies": { "debug": { @@ -565,8 +569,8 @@ "integrity": "sha1-snA2LNiLGkitMIl2zn+lTphBF0Y=", "dev": true, "requires": { - "debug": "2.6.9", - "pkg-dir": "1.0.0" + "debug": "^2.6.8", + "pkg-dir": "^1.0.0" }, "dependencies": { "debug": { @@ -586,16 +590,16 @@ "integrity": "sha512-t6hGKQDMIt9N8R7vLepsYXgDfeuhp6ZJSgtrLEDxonpSubyxUZHjhm6LsAaZX8q6GYVxkbT3kTsV9G5mBCFR6A==", "dev": true, "requires": { - "contains-path": "0.1.0", - "debug": "2.6.9", + "contains-path": "^0.1.0", + "debug": "^2.6.8", "doctrine": "1.5.0", - "eslint-import-resolver-node": "0.3.2", - "eslint-module-utils": "2.2.0", - "has": "1.0.3", - "lodash": "4.17.10", - "minimatch": "3.0.4", - "read-pkg-up": "2.0.0", - "resolve": "1.8.1" + "eslint-import-resolver-node": "^0.3.1", + "eslint-module-utils": "^2.2.0", + "has": "^1.0.1", + "lodash": "^4.17.4", + "minimatch": "^3.0.3", + "read-pkg-up": "^2.0.0", + "resolve": "^1.6.0" }, "dependencies": { "debug": { @@ -613,8 +617,8 @@ "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", "dev": true, "requires": { - "esutils": "2.0.2", - "isarray": "1.0.0" + "esutils": "^2.0.2", + "isarray": "^1.0.0" } } } @@ -631,8 +635,8 @@ "integrity": "sha512-W+B0SvF4gamyCTmUc+uITPY0989iXVfKvhwtmJocTaYoc/3khEHmEmvfY/Gn9HA9VV75jrQECsHizkNw1b68FA==", "dev": true, "requires": { - "esrecurse": "4.2.1", - "estraverse": "4.2.0" + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" } }, "eslint-visitor-keys": { @@ -647,8 +651,8 @@ "integrity": "sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A==", "dev": true, "requires": { - "acorn": "5.7.1", - "acorn-jsx": "3.0.1" + "acorn": "^5.5.0", + "acorn-jsx": "^3.0.0" } }, "esprima": { @@ -663,7 +667,7 @@ "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", "dev": true, "requires": { - "estraverse": "4.2.0" + "estraverse": "^4.0.0" } }, "esrecurse": { @@ -672,7 +676,7 @@ "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", "dev": true, "requires": { - "estraverse": "4.2.0" + "estraverse": "^4.1.0" } }, "estraverse": { @@ -703,9 +707,9 @@ "integrity": "sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==", "dev": true, "requires": { - "chardet": "0.4.2", - "iconv-lite": "0.4.23", - "tmp": "0.0.33" + "chardet": "^0.4.0", + "iconv-lite": "^0.4.17", + "tmp": "^0.0.33" } }, "extsprintf": { @@ -735,7 +739,7 @@ "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", "dev": true, "requires": { - "escape-string-regexp": "1.0.5" + "escape-string-regexp": "^1.0.5" } }, "file-entry-cache": { @@ -744,23 +748,18 @@ "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", "dev": true, "requires": { - "flat-cache": "1.3.0", - "object-assign": "4.1.1" + "flat-cache": "^1.2.1", + "object-assign": "^4.0.1" } }, - "file-type": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-10.2.0.tgz", - "integrity": "sha512-eqX81S1PWdLDPW39yyB214TVVOsUQjSmPcyUjeVH6ksH+94Y2YA/ItiIwa53rJiSofJZLK6lGsuCE3rwt0vp4w==" - }, "find-up": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", "dev": true, "requires": { - "path-exists": "2.1.0", - "pinkie-promise": "2.0.1" + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" } }, "flat-cache": { @@ -769,10 +768,10 @@ "integrity": "sha1-0wMLMrOBVPTjt+nHCfSQ9++XxIE=", "dev": true, "requires": { - "circular-json": "0.3.3", - "del": "2.2.2", - "graceful-fs": "4.1.11", - "write": "0.2.1" + "circular-json": "^0.3.1", + "del": "^2.0.2", + "graceful-fs": "^4.1.2", + "write": "^0.2.1" } }, "forever-agent": { @@ -785,9 +784,19 @@ "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz", "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=", "requires": { - "asynckit": "0.4.0", + "asynckit": "^0.4.0", "combined-stream": "1.0.6", - "mime-types": "2.1.19" + "mime-types": "^2.1.12" + } + }, + "fs-extra": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-6.0.1.tgz", + "integrity": "sha512-GnyIkKhhzXZUWFCaJzvyDLEEgDkPfb4/TPvJCJVuS8MWZgoSsErf++QpiAlDnKFcqhRlm+tIOcencCjyJE6ZCA==", + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" } }, "fs.realpath": { @@ -818,7 +827,7 @@ "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", "requires": { - "assert-plus": "1.0.0" + "assert-plus": "^1.0.0" } }, "glob": { @@ -827,12 +836,12 @@ "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", "dev": true, "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" } }, "globals": { @@ -847,19 +856,18 @@ "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", "dev": true, "requires": { - "array-union": "1.0.2", - "arrify": "1.0.1", - "glob": "7.1.2", - "object-assign": "4.1.1", - "pify": "2.3.0", - "pinkie-promise": "2.0.1" + "array-union": "^1.0.1", + "arrify": "^1.0.0", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" } }, "graceful-fs": { "version": "4.1.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", - "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", - "dev": true + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=" }, "har-schema": { "version": "2.0.0", @@ -871,8 +879,8 @@ "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", "requires": { - "ajv": "5.5.2", - "har-schema": "2.0.0" + "ajv": "^5.1.0", + "har-schema": "^2.0.0" } }, "has": { @@ -881,7 +889,7 @@ "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", "dev": true, "requires": { - "function-bind": "1.1.1" + "function-bind": "^1.1.1" } }, "has-ansi": { @@ -890,7 +898,7 @@ "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", "dev": true, "requires": { - "ansi-regex": "2.1.1" + "ansi-regex": "^2.0.0" } }, "has-flag": { @@ -915,9 +923,9 @@ "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", "requires": { - "assert-plus": "1.0.0", - "jsprim": "1.4.1", - "sshpk": "1.14.2" + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" } }, "iconv-lite": { @@ -926,7 +934,7 @@ "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", "dev": true, "requires": { - "safer-buffer": "2.1.2" + "safer-buffer": ">= 2.1.2 < 3" } }, "ieee754": { @@ -952,8 +960,8 @@ "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "dev": true, "requires": { - "once": "1.4.0", - "wrappy": "1.0.2" + "once": "^1.3.0", + "wrappy": "1" } }, "inherits": { @@ -968,20 +976,20 @@ "integrity": "sha512-h+xtnyk4EwKvFWHrUYsWErEVR+igKtLdchu+o0Z1RL7VU/jVMFbYir2bp6bAj8efFNxWqHX0dIss6fJQ+/+qeQ==", "dev": true, "requires": { - "ansi-escapes": "3.1.0", - "chalk": "2.4.1", - "cli-cursor": "2.1.0", - "cli-width": "2.2.0", - "external-editor": "2.2.0", - "figures": "2.0.0", - "lodash": "4.17.10", + "ansi-escapes": "^3.0.0", + "chalk": "^2.0.0", + "cli-cursor": "^2.1.0", + "cli-width": "^2.0.0", + "external-editor": "^2.0.4", + "figures": "^2.0.0", + "lodash": "^4.3.0", "mute-stream": "0.0.7", - "run-async": "2.3.0", - "rx-lite": "4.0.8", - "rx-lite-aggregates": "4.0.8", - "string-width": "2.1.1", - "strip-ansi": "4.0.0", - "through": "2.3.8" + "run-async": "^2.2.0", + "rx-lite": "^4.0.8", + "rx-lite-aggregates": "^4.0.8", + "string-width": "^2.1.0", + "strip-ansi": "^4.0.0", + "through": "^2.3.6" } }, "is-arrayish": { @@ -1001,7 +1009,7 @@ "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", "dev": true, "requires": { - "builtin-modules": "1.1.1" + "builtin-modules": "^1.0.0" } }, "is-fullwidth-code-point": { @@ -1022,7 +1030,7 @@ "integrity": "sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==", "dev": true, "requires": { - "is-path-inside": "1.0.1" + "is-path-inside": "^1.0.0" } }, "is-path-inside": { @@ -1031,7 +1039,7 @@ "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", "dev": true, "requires": { - "path-is-inside": "1.0.2" + "path-is-inside": "^1.0.1" } }, "is-promise": { @@ -1084,8 +1092,8 @@ "integrity": "sha512-PIt2cnwmPfL4hKNwqeiuz4bKfnzHTBv6HyVgjahA6mPLwPDzjDWrplJBMjHUFxku/N3FlmrbyPclad+I+4mJ3A==", "dev": true, "requires": { - "argparse": "1.0.10", - "esprima": "4.0.1" + "argparse": "^1.0.7", + "esprima": "^4.0.0" } }, "jsbn": { @@ -1120,6 +1128,14 @@ "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=" }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "requires": { + "graceful-fs": "^4.1.6" + } + }, "jsprim": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", @@ -1137,8 +1153,8 @@ "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", "dev": true, "requires": { - "prelude-ls": "1.1.2", - "type-check": "0.3.2" + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" } }, "load-json-file": { @@ -1147,10 +1163,10 @@ "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", "dev": true, "requires": { - "graceful-fs": "4.1.11", - "parse-json": "2.2.0", - "pify": "2.3.0", - "strip-bom": "3.0.0" + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "strip-bom": "^3.0.0" } }, "locate-path": { @@ -1159,8 +1175,8 @@ "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", "dev": true, "requires": { - "p-locate": "2.0.0", - "path-exists": "3.0.0" + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" }, "dependencies": { "path-exists": { @@ -1183,15 +1199,10 @@ "integrity": "sha512-fFEhvcgzuIoJVUF8fYr5KR0YqxD238zgObTps31YdADwPPAp82a4M8TrckkWyx7ekNlf9aBcVn81cFwwXngrJA==", "dev": true, "requires": { - "pseudomap": "1.0.2", - "yallist": "2.1.2" + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" } }, - "media-typer": { - "version": "0.3.0", - "resolved": "http://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" - }, "mime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", @@ -1207,7 +1218,7 @@ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.19.tgz", "integrity": "sha512-P1tKYHVSZ6uFo26mtnve4HQFE3koh1UWVkp8YUC+ESBHe945xWSoXuHHiGarDqcEZ+whpCDnlNw5LON0kLo+sw==", "requires": { - "mime-db": "1.35.0" + "mime-db": "~1.35.0" } }, "mimic-fn": { @@ -1222,7 +1233,7 @@ "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "dev": true, "requires": { - "brace-expansion": "1.1.11" + "brace-expansion": "^1.1.7" } }, "minimist": { @@ -1250,34 +1261,25 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, "music-metadata": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/music-metadata/-/music-metadata-3.1.5.tgz", - "integrity": "sha512-ZR+sy0Bu6iojocbEQ6mH/UTfpD9OCkCNcbbewdycOX4oF8iNEwp0TXtASmAW9Y4xKIcsgWjI/e5yzWfpu+ue1Q==", - "requires": { - "debug": "4.1.0", - "file-type": "10.2.0", - "media-typer": "0.3.0", - "strtok3": "2.1.1", - "then-read-stream": "1.3.1", - "token-types": "1.0.1" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/music-metadata/-/music-metadata-1.1.0.tgz", + "integrity": "sha512-HTIWhewHyLwVQByhh45BGtmxrEDpq4RI4ZwUKYBwj7uPheE+MmudlnRc22emq99GRLGmb16/Q7RgM+3O0Wi2wg==", + "requires": { + "bluebird": "^3.5.1", + "debug": "^3.1.0", + "fs-extra": "^6.0.1", + "strtok3": "^1.4.2", + "then-read-stream": "^1.1.3", + "token-types": "^0.9.4" }, "dependencies": { - "debug": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.0.tgz", - "integrity": "sha512-heNPJUJIqC+xB6ayLAMHaIrmN9HKa7aQO8MGqKpvCA+uJYVcvR6l5kgdrhRuwPFHU7P5/A1w0BjByPHwpfTDKg==", - "requires": { - "ms": "2.1.1" - } - }, - "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + "bluebird": { + "version": "3.5.5", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.5.tgz", + "integrity": "sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w==" } } }, @@ -1298,9 +1300,9 @@ "resolved": "https://registry.npmjs.org/node-static/-/node-static-0.7.10.tgz", "integrity": "sha512-bd7zO5hvCWzdglgwz9t82T4mYTEUzEG5pXnSqEzitvmEacusbhl8/VwuCbMaYR9g2PNK5191yBtAEQLJEmQh1A==", "requires": { - "colors": "1.3.1", - "mime": "1.6.0", - "optimist": "0.6.1" + "colors": ">=0.6.0", + "mime": "^1.2.9", + "optimist": ">=0.3.4" } }, "normalize-package-data": { @@ -1309,10 +1311,10 @@ "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==", "dev": true, "requires": { - "hosted-git-info": "2.7.1", - "is-builtin-module": "1.0.0", - "semver": "5.5.0", - "validate-npm-package-license": "3.0.3" + "hosted-git-info": "^2.1.4", + "is-builtin-module": "^1.0.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" } }, "oauth-sign": { @@ -1332,7 +1334,7 @@ "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dev": true, "requires": { - "wrappy": "1.0.2" + "wrappy": "1" } }, "onetime": { @@ -1341,7 +1343,7 @@ "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", "dev": true, "requires": { - "mimic-fn": "1.2.0" + "mimic-fn": "^1.0.0" } }, "optimist": { @@ -1349,8 +1351,8 @@ "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", "requires": { - "minimist": "0.0.10", - "wordwrap": "0.0.3" + "minimist": "~0.0.1", + "wordwrap": "~0.0.2" } }, "optionator": { @@ -1359,12 +1361,12 @@ "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", "dev": true, "requires": { - "deep-is": "0.1.3", - "fast-levenshtein": "2.0.6", - "levn": "0.3.0", - "prelude-ls": "1.1.2", - "type-check": "0.3.2", - "wordwrap": "1.0.0" + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.4", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "wordwrap": "~1.0.0" }, "dependencies": { "wordwrap": { @@ -1387,7 +1389,7 @@ "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", "dev": true, "requires": { - "p-try": "1.0.0" + "p-try": "^1.0.0" } }, "p-locate": { @@ -1396,7 +1398,7 @@ "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", "dev": true, "requires": { - "p-limit": "1.3.0" + "p-limit": "^1.1.0" } }, "p-try": { @@ -1411,7 +1413,7 @@ "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", "dev": true, "requires": { - "error-ex": "1.3.2" + "error-ex": "^1.2.0" } }, "path-exists": { @@ -1420,7 +1422,7 @@ "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", "dev": true, "requires": { - "pinkie-promise": "2.0.1" + "pinkie-promise": "^2.0.0" } }, "path-is-absolute": { @@ -1447,7 +1449,7 @@ "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", "dev": true, "requires": { - "pify": "2.3.0" + "pify": "^2.0.0" } }, "performance-now": { @@ -1473,7 +1475,7 @@ "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", "dev": true, "requires": { - "pinkie": "2.0.4" + "pinkie": "^2.0.0" } }, "pkg-dir": { @@ -1482,7 +1484,7 @@ "integrity": "sha1-ektQio1bstYp1EcFb/TpyTFM89Q=", "dev": true, "requires": { - "find-up": "1.1.2" + "find-up": "^1.0.0" } }, "pluralize": { @@ -1536,9 +1538,9 @@ "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", "dev": true, "requires": { - "load-json-file": "2.0.0", - "normalize-package-data": "2.4.0", - "path-type": "2.0.0" + "load-json-file": "^2.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^2.0.0" } }, "read-pkg-up": { @@ -1547,8 +1549,8 @@ "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", "dev": true, "requires": { - "find-up": "2.1.0", - "read-pkg": "2.0.0" + "find-up": "^2.0.0", + "read-pkg": "^2.0.0" }, "dependencies": { "find-up": { @@ -1557,7 +1559,7 @@ "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", "dev": true, "requires": { - "locate-path": "2.0.0" + "locate-path": "^2.0.0" } } } @@ -1568,13 +1570,13 @@ "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "2.0.0", - "safe-buffer": "5.1.2", - "string_decoder": "1.1.1", - "util-deprecate": "1.0.2" + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" } }, "regexpp": { @@ -1588,26 +1590,26 @@ "resolved": "https://registry.npmjs.org/request/-/request-2.87.0.tgz", "integrity": "sha512-fcogkm7Az5bsS6Sl0sibkbhcKsnyon/jV1kF3ajGmF0c8HrttdKTPRT9hieOaQHA5HEq6r8OyWOo/o781C1tNw==", "requires": { - "aws-sign2": "0.7.0", - "aws4": "1.7.0", - "caseless": "0.12.0", - "combined-stream": "1.0.6", - "extend": "3.0.2", - "forever-agent": "0.6.1", - "form-data": "2.3.2", - "har-validator": "5.0.3", - "http-signature": "1.2.0", - "is-typedarray": "1.0.0", - "isstream": "0.1.2", - "json-stringify-safe": "5.0.1", - "mime-types": "2.1.19", - "oauth-sign": "0.8.2", - "performance-now": "2.1.0", - "qs": "6.5.2", - "safe-buffer": "5.1.2", - "tough-cookie": "2.3.4", - "tunnel-agent": "0.6.0", - "uuid": "3.3.2" + "aws-sign2": "~0.7.0", + "aws4": "^1.6.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.5", + "extend": "~3.0.1", + "forever-agent": "~0.6.1", + "form-data": "~2.3.1", + "har-validator": "~5.0.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.17", + "oauth-sign": "~0.8.2", + "performance-now": "^2.1.0", + "qs": "~6.5.1", + "safe-buffer": "^5.1.1", + "tough-cookie": "~2.3.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.1.0" } }, "request-promise": { @@ -1615,10 +1617,10 @@ "resolved": "https://registry.npmjs.org/request-promise/-/request-promise-1.0.2.tgz", "integrity": "sha1-FV9BBgjZJXwInB0LJvjY96iqhqE=", "requires": { - "bluebird": "2.11.0", - "cls-bluebird": "1.1.3", - "lodash": "3.10.1", - "request": "2.87.0" + "bluebird": "^2.3", + "cls-bluebird": "^1.0.1", + "lodash": "^3.10.0", + "request": "^2.34" }, "dependencies": { "lodash": { @@ -1634,8 +1636,8 @@ "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", "dev": true, "requires": { - "caller-path": "0.1.0", - "resolve-from": "1.0.1" + "caller-path": "^0.1.0", + "resolve-from": "^1.0.0" } }, "resolve": { @@ -1644,7 +1646,7 @@ "integrity": "sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA==", "dev": true, "requires": { - "path-parse": "1.0.5" + "path-parse": "^1.0.5" } }, "resolve-from": { @@ -1659,8 +1661,8 @@ "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", "dev": true, "requires": { - "onetime": "2.0.1", - "signal-exit": "3.0.2" + "onetime": "^2.0.0", + "signal-exit": "^3.0.2" } }, "rimraf": { @@ -1669,7 +1671,7 @@ "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", "dev": true, "requires": { - "glob": "7.1.2" + "glob": "^7.0.5" } }, "run-async": { @@ -1678,7 +1680,7 @@ "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", "dev": true, "requires": { - "is-promise": "2.1.0" + "is-promise": "^2.1.0" } }, "rx-lite": { @@ -1693,7 +1695,7 @@ "integrity": "sha1-dTuHqJoRyVRnxKwWJsTvxOBcZ74=", "dev": true, "requires": { - "rx-lite": "4.0.8" + "rx-lite": "*" } }, "safe-buffer": { @@ -1723,7 +1725,7 @@ "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", "dev": true, "requires": { - "shebang-regex": "1.0.0" + "shebang-regex": "^1.0.0" } }, "shebang-regex": { @@ -1749,14 +1751,14 @@ "integrity": "sha512-POqxBK6Lb3q6s047D/XsDVNPnF9Dl8JSaqe9h9lURl0OdNqy/ujDrOiIHtsqXMGbWWTIomRzAMaTyawAU//Reg==", "dev": true, "requires": { - "is-fullwidth-code-point": "2.0.0" + "is-fullwidth-code-point": "^2.0.0" } }, "sonos-discovery": { - "version": "https://github.com/jishi/node-sonos-discovery/archive/v1.7.1.tar.gz", - "integrity": "sha512-1i8FAbLBajwe0i9nWqVEjvguM8ltfz7Gnc6yaURyCyKYuodoH3KicrLD6xoyi4BYxGyrs7sgTiNDFO0b489erg==", + "version": "https://github.com/jishi/node-sonos-discovery/archive/v1.7.3.tar.gz", + "integrity": "sha512-bHog/7+zjirplyZOjclSM9g6rZVZX0bDWX+PzkS6KN0I1/KK6SjotazhkI4nhcrx4NYWnxCQULIO9RvzlyG2uA==", "requires": { - "html-entities": "1.0.10", + "html-entities": "1.0.x", "xml-flow": "1.0.2" }, "dependencies": { @@ -1773,8 +1775,8 @@ "integrity": "sha512-N19o9z5cEyc8yQQPukRCZ9EUmb4HUpnrmaL/fxS2pBo2jbfcFRVuFZ/oFC+vZz0MNNk0h80iMn5/S6qGZOL5+g==", "dev": true, "requires": { - "spdx-expression-parse": "3.0.0", - "spdx-license-ids": "3.0.0" + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" } }, "spdx-exceptions": { @@ -1789,8 +1791,8 @@ "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", "dev": true, "requires": { - "spdx-exceptions": "2.1.0", - "spdx-license-ids": "3.0.0" + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" } }, "spdx-license-ids": { @@ -1810,15 +1812,15 @@ "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.2.tgz", "integrity": "sha1-xvxhZIo9nE52T9P8306hBeSSupg=", "requires": { - "asn1": "0.2.3", - "assert-plus": "1.0.0", - "bcrypt-pbkdf": "1.0.2", - "dashdash": "1.14.1", - "ecc-jsbn": "0.1.2", - "getpass": "0.1.7", - "jsbn": "0.1.1", - "safer-buffer": "2.1.2", - "tweetnacl": "0.14.5" + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" } }, "string-width": { @@ -1827,8 +1829,8 @@ "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", "dev": true, "requires": { - "is-fullwidth-code-point": "2.0.0", - "strip-ansi": "4.0.0" + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" } }, "string_decoder": { @@ -1837,7 +1839,7 @@ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { - "safe-buffer": "5.1.2" + "safe-buffer": "~5.1.0" } }, "strip-ansi": { @@ -1846,7 +1848,7 @@ "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "dev": true, "requires": { - "ansi-regex": "3.0.0" + "ansi-regex": "^3.0.0" }, "dependencies": { "ansi-regex": { @@ -1870,27 +1872,20 @@ "dev": true }, "strtok3": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-2.1.1.tgz", - "integrity": "sha512-fASvEpC2aDaTS6TZHffbW4w8li471ws3qQc0ag1KdftigxvOxhjIF8NWiDLSiWQV8YQSKhZggq8YbGTzvicCHA==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-1.6.0.tgz", + "integrity": "sha512-BDr7b5RJSfvX1wBjJDwpJOGZ0EhfhZaMCm+YaOVAcvFgo8/+nJq/Vurzh7JykAgiW+EOo5kkrfVffBFFp3u8TQ==", "requires": { - "debug": "4.1.0", - "then-read-stream": "1.3.1", - "token-types": "1.0.1" + "debug": "^3.1.0", + "es6-promise": "^4.2.4", + "then-read-stream": "^1.2.1", + "token-types": "^0.10.0" }, "dependencies": { - "debug": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.0.tgz", - "integrity": "sha512-heNPJUJIqC+xB6ayLAMHaIrmN9HKa7aQO8MGqKpvCA+uJYVcvR6l5kgdrhRuwPFHU7P5/A1w0BjByPHwpfTDKg==", - "requires": { - "ms": "2.1.1" - } - }, - "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + "token-types": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/token-types/-/token-types-0.10.0.tgz", + "integrity": "sha512-26A0816VoHW8h64OT47dcclsH5M9iwo9zi3KoCPz1NrKoI9T2dlVkisgzTaGK4dPjCbP3ugf7cYqhWOiAzVHgw==" } } }, @@ -1906,12 +1901,12 @@ "integrity": "sha512-UUkEAPdSGxtRpiV9ozJ5cMTtYiqz7Ni1OGqLXRCynrvzdtR1p+cfOWe2RJLwvUG8hNanaSRjecIqwOjqeatDsA==", "dev": true, "requires": { - "ajv": "5.5.2", - "ajv-keywords": "2.1.1", - "chalk": "2.4.1", - "lodash": "4.17.10", + "ajv": "^5.2.3", + "ajv-keywords": "^2.1.0", + "chalk": "^2.1.0", + "lodash": "^4.17.4", "slice-ansi": "1.0.0", - "string-width": "2.1.1" + "string-width": "^2.1.1" } }, "text-table": { @@ -1921,9 +1916,9 @@ "dev": true }, "then-read-stream": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/then-read-stream/-/then-read-stream-1.3.1.tgz", - "integrity": "sha512-RDxUU490df9+DlL0qd1Uc8EpKOwHQodjh5Gd2qZ249k4zYyv8FLgb3g9ygII1EItT35SDOK1LE3vX2zNZKCSXA==" + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/then-read-stream/-/then-read-stream-1.5.1.tgz", + "integrity": "sha512-I+iiemYWhp1ysJQEioqpEICgvHlqHS5WrQGZkboFLs7Jm350Kvq4cN3qRCzHpETUuq5+NsdrdWEg6M0NFxtwtQ==" }, "through": { "version": "2.3.8", @@ -1937,20 +1932,20 @@ "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", "dev": true, "requires": { - "os-tmpdir": "1.0.2" + "os-tmpdir": "~1.0.2" } }, "token-types": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/token-types/-/token-types-1.0.1.tgz", - "integrity": "sha512-Q2yPT4GA4gmIAgUrL9O9LRzKFKJ8FA1P5eiOfC2eH+IZMSJVQBCML1SOooeRicIIvTr/UZuTAX5PqMzgBXJOvg==" + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/token-types/-/token-types-0.9.4.tgz", + "integrity": "sha512-KSl/Q1GJ4FoxbqKCLhTiIowVzom2cP0fgWGXKsJupbJQqfnCDmxkdMopIrt+y5Ak6YAGdy9TKpplWDioaBsbEw==" }, "tough-cookie": { "version": "2.3.4", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz", "integrity": "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==", "requires": { - "punycode": "1.4.1" + "punycode": "^1.4.1" } }, "tunnel-agent": { @@ -1958,7 +1953,7 @@ "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", "requires": { - "safe-buffer": "5.1.2" + "safe-buffer": "^5.0.1" } }, "tweetnacl": { @@ -1973,7 +1968,7 @@ "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", "dev": true, "requires": { - "prelude-ls": "1.1.2" + "prelude-ls": "~1.1.2" } }, "typedarray": { @@ -1987,6 +1982,11 @@ "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.1.tgz", "integrity": "sha512-5/4etnCkd9c8gwgowi5/om/mYO5ajCaOgdzj/oW+0eQV9WxKBDZw5+ycmKmeaTXjInS/W0BzpGLo2xR2aBwZdg==" }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" + }, "url": { "version": "0.10.3", "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", @@ -2020,8 +2020,8 @@ "integrity": "sha512-63ZOUnL4SIXj4L0NixR3L1lcjO38crAbgrTpl28t8jjrfuiOBL5Iygm+60qPs/KsZGzPNg6Smnc/oY16QTjF0g==", "dev": true, "requires": { - "spdx-correct": "3.0.0", - "spdx-expression-parse": "3.0.0" + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" } }, "verror": { @@ -2029,9 +2029,9 @@ "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", "requires": { - "assert-plus": "1.0.0", + "assert-plus": "^1.0.0", "core-util-is": "1.0.2", - "extsprintf": "1.3.0" + "extsprintf": "^1.2.0" } }, "wav-file-info": { @@ -2045,7 +2045,7 @@ "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", "dev": true, "requires": { - "isexe": "2.0.0" + "isexe": "^2.0.0" } }, "wordwrap": { @@ -2065,7 +2065,7 @@ "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", "dev": true, "requires": { - "mkdirp": "0.5.1" + "mkdirp": "^0.5.1" } }, "xml-flow": { @@ -2073,7 +2073,7 @@ "resolved": "https://registry.npmjs.org/xml-flow/-/xml-flow-1.0.2.tgz", "integrity": "sha512-TfV52Su2083n8w61l1hGI5K3ZAkn4hmaon3oJHc+TRQo0QkKLMPAPdMpGMJd+5Aik6ZUicPDjBINO8QxLkZGgg==", "requires": { - "sax": "1.2.4" + "sax": "^1.2.4" }, "dependencies": { "sax": { @@ -2088,8 +2088,8 @@ "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", "requires": { - "sax": "1.2.1", - "xmlbuilder": "9.0.7" + "sax": ">=0.6.0", + "xmlbuilder": "~9.0.1" } }, "xmlbuilder": { diff --git a/package.json b/package.json index 6ff067d8..af9afe88 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sonos-http-api", - "version": "1.6.8", + "version": "1.6.9", "description": "A simple node app for controlling a Sonos system with basic HTTP requests", "scripts": { "start": "node server.js", @@ -22,7 +22,7 @@ "music-metadata": "^1.1.0", "node-static": "^0.7.9", "request-promise": "~1.0.2", - "sonos-discovery": "https://github.com/jishi/node-sonos-discovery/archive/v1.7.2.tar.gz", + "sonos-discovery": "https://github.com/jishi/node-sonos-discovery/archive/v1.7.3.tar.gz", "wav-file-info": "0.0.8" }, "engines": { From dae63a35a3b744b2034a3de0d19a5d7ff01d90a8 Mon Sep 17 00:00:00 2001 From: David De Sloovere Date: Sun, 18 Aug 2019 12:32:10 +0200 Subject: [PATCH 087/123] Add Spotify link info Some more info regarding Spotify links/URIs --- README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/README.md b/README.md index c885d5ee..5aea0e83 100644 --- a/README.md +++ b/README.md @@ -797,8 +797,23 @@ The following endpoints are available: /RoomName/amazonmusic/{now,next,queue}/album:{albumID} ``` +**Spotify** + +You can find the **Spotify** track and album IDs as the last part of the URL. + +How to find the URL? +- Web player: the address bar URL for albums and playlist; select _Copy Song Link_ from the dot menu. +- Desktop client: via _Share > Copy {Album,Playlist,Song} Link_ +- Mobile client: via _Share > Copy Link_ + +For Spotify playlists, you'll need this format: `spotify:user:spotify:playlist:{playlistid}`. Even if it's a public playlist, you always need to prefix with `spotify:user:`. An example of a great playlist: `/kitchen/spotify/now/spotify:user:spotify:playlist:32O0SSXDNWDrMievPkV0Im`. + +To get more technical, you actually use the Spotify URI (not URL) for the endpoint, like so: `{room}/spotify/{now,next,queue}/{spotifyuri}`. + It only handles a single **spotify** account currently. It will probably use the first account added on your system. +**Apple Music** + You can find **Apple Music** song and album IDs via the [iTunes Search API](https://affiliate.itunes.apple.com/resources/documentation/itunes-store-web-service-search-api/). @@ -814,6 +829,8 @@ The format is: https://itunes.apple.com/de/album/{songName}/{albumID}?i={songID} The format is: https://itunes.apple.com/de/album/{albumName}/{albumID} > eg: https://itunes.apple.com/de/album/f-g-restless/355363490 +**Amazon Music** + To find **Amazon Music** song and album IDs you can use the Amazon Music App, search for a song or an album and share a link. Look at the link you just shared. This works with Amazon Music Prime as well as with Amazon Music Prime which is included in your Amazon Prime membership. From 7808d1f400d81adc60e28fffc561a2d56ccec631 Mon Sep 17 00:00:00 2001 From: Roger Date: Fri, 24 Jan 2020 12:01:23 +0100 Subject: [PATCH 088/123] Add household setting to readme I've been looking for this feature and had to find out about it through the issues, seemed a good one to have in the readme :) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 5aea0e83..65feb6a6 100644 --- a/README.md +++ b/README.md @@ -318,6 +318,7 @@ Available options are: * auth: require basic auth credentials which requires a username and password * announceVolume: the percentual volume use when invoking say/sayall without any volume parameter * presetDir: absolute path to look for presets (folder must exist!) +* household: when theres multiple sonos accounts on one network (example: Sonos_ab7d67898dcc5a6d, find it in [Your sonos IP]:1400/status/zp) Example: From 2ca0ee3e43e3d7b205c5b561459acd33f6e0ef63 Mon Sep 17 00:00:00 2001 From: rummens <31141785+rummens@users.noreply.github.com> Date: Thu, 23 Apr 2020 10:14:02 +0200 Subject: [PATCH 089/123] Enhanced documentation for presets Added trackNo and elapsedTime to preset documentation, as they are supported but not documented. --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 65feb6a6..dff360d1 100644 --- a/README.md +++ b/README.md @@ -293,6 +293,8 @@ Example content: "volume": 15 } ], + "trackNo": 3, + "elapsedTime": 42, "playMode": { "shuffle": true, "repeat": "all", From 91284cbdeb8db0e26c3d3bff9060b59e4b406d6c Mon Sep 17 00:00:00 2001 From: Jimmy Shimizu Date: Tue, 5 May 2020 12:34:14 +0200 Subject: [PATCH 090/123] Update README.md --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index dff360d1..430274b9 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,10 @@ [![PayPal donate button](https://img.shields.io/badge/paypal-donate-yellow.svg)](https://www.paypal.me/jishi "Donate once-off to this project using Paypal") [![Join the chat at gitter](https://img.shields.io/gitter/room/badges/shields.svg)](https://gitter.im/node-sonos-http-api/Lobby "Need assistance? Join the chat at Gitter.im") +⚠WARNING!⚠ + +The upcoming Sonos software update (dubbed S2) will most likely break this API. If you rely on it, and want to continue use it, I would hold of upgrading to S2 right now until we know more. + + Feel free to use it as you please. Consider donating if you want to support further development. Reach out on the gitter chat if you have issues getting it to run, instead of creating new issues, thank you! If you are also looking for cloud control (ifttt, public webhooks etc), see the [bronos-client](http://www.bronos.net) project! That pi image also contains an installation of this http-api. From 7b5d5e45863a4ea88a34aab3e554e282ab93b945 Mon Sep 17 00:00:00 2001 From: Jimmy Shimizu Date: Mon, 22 Jun 2020 10:40:18 +0200 Subject: [PATCH 091/123] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 430274b9..997f6a59 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ⚠WARNING!⚠ -The upcoming Sonos software update (dubbed S2) will most likely break this API. If you rely on it, and want to continue use it, I would hold of upgrading to S2 right now until we know more. +The upcoming Sonos software update (dubbed S2) seems to still work. However, it might break in the future if and when they decide to drop UPnP as control protocol. Feel free to use it as you please. Consider donating if you want to support further development. Reach out on the gitter chat if you have issues getting it to run, instead of creating new issues, thank you! From 8789ed81e53d2c018f8b28d48466c564d5146043 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98ystein=20Blixhavn?= Date: Fri, 29 Jan 2021 15:35:56 +0100 Subject: [PATCH 092/123] Update spotify/now/album|playlist to always set AVtransport to queue --- lib/actions/spotify.js | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/lib/actions/spotify.js b/lib/actions/spotify.js index dbbd8c5a..6b281489 100644 --- a/lib/actions/spotify.js +++ b/lib/actions/spotify.js @@ -29,15 +29,10 @@ function spotify(player, values) { } else if (action == 'now') { var nextTrackNo = player.coordinator.state.trackNo + 1; let promise = Promise.resolve(); - if (player.coordinator.avTransportUri.startsWith('x-rincon-queue') === false) { - promise = promise.then(() => player.coordinator.setAVTransport(`x-rincon-queue:${player.coordinator.uuid}#0`)); - } - - return promise.then(() => { - return player.coordinator.addURIToQueue(uri, metadata, true, nextTrackNo) - .then((addToQueueStatus) => player.coordinator.trackSeek(addToQueueStatus.firsttracknumberenqueued)) - .then(() => player.coordinator.play()); - }); + return promise.then(() => player.coordinator.setAVTransport(`x-rincon-queue:${player.coordinator.uuid}#0`)) + .then(() => player.coordinator.addURIToQueue(uri, metadata, true, nextTrackNo)) + .then((addToQueueStatus) => player.coordinator.trackSeek(addToQueueStatus.firsttracknumberenqueued)) + .then(() => player.coordinator.play()); } else if (action == 'next') { var nextTrackNo = player.coordinator.state.trackNo + 1; From 03160925a988d694a4fd9c906f0a83227bafad1d Mon Sep 17 00:00:00 2001 From: Thyraz Date: Thu, 3 Sep 2020 21:22:03 +0200 Subject: [PATCH 093/123] Updates fuse.js for more reliable search results --- lib/actions/pandora.js | 6 +++--- lib/actions/siriusXM.js | 4 ++-- lib/music_services/libraryDef.js | 18 +++++++++--------- package-lock.json | 6 +++--- package.json | 2 +- 5 files changed, 18 insertions(+), 18 deletions(-) diff --git a/lib/actions/pandora.js b/lib/actions/pandora.js index 2f498ed3..faa1772e 100644 --- a/lib/actions/pandora.js +++ b/lib/actions/pandora.js @@ -89,11 +89,11 @@ function pandora(player, values) { if (results.length > 0) { const station = results[0]; if (station.type == undefined) { - uri = getPandoraUri(station.stationId, station.stationName, station.artUrl); - metadata = getPandoraMetadata(station.stationId, station.stationName, player.system.getServiceType('Pandora')); + uri = getPandoraUri(station.item.stationId, station.item.stationName, station.item.artUrl); + metadata = getPandoraMetadata(station.item.stationId, station.item.stationName, player.system.getServiceType('Pandora')); return Promise.resolve(); } else { - return pandoraAPI("station.createStation", {"musicToken":station.stationId, "musicType":station.type}) + return pandoraAPI("station.createStation", {"musicToken":station.item.stationId, "musicType":station.item.type}) .then((stationInfo) => { uri = getPandoraUri(stationInfo.stationId); metadata = getPandoraMetadata(stationInfo.stationId, stationInfo.stationName, player.system.getServiceType('Pandora')); diff --git a/lib/actions/siriusXM.js b/lib/actions/siriusXM.js index 0930ec67..9e3dc168 100644 --- a/lib/actions/siriusXM.js +++ b/lib/actions/siriusXM.js @@ -72,8 +72,8 @@ function siriusXM(player, values) { results = fuzzy.search(searchVal); if (results.length > 0) { const channel = results[0]; - const uri = getSiriusXmUri(channel.id); - const metadata = getSiriusXmMetadata(channel.id, channel.parentID, channel.fullTitle); + const uri = getSiriusXmUri(channel.item.id); + const metadata = getSiriusXmMetadata(channel.item.id, channel.item.parentID, channel.item.fullTitle); return player.coordinator.setAVTransport(uri, metadata) .then(() => player.coordinator.play()); diff --git a/lib/music_services/libraryDef.js b/lib/music_services/libraryDef.js index a12c0d2e..50ee2b84 100644 --- a/lib/music_services/libraryDef.js +++ b/lib/music_services/libraryDef.js @@ -99,7 +99,7 @@ function loadTracks(type, tracksJson) { }; if (tracksJson.length > 0) { - var albumName = tracksJson[0].albumName; + var albumName = tracksJson[0].item.albumName; // Filtered list of tracks to play tracks.queueTracks = tracksJson.reduce(function(tracksArray, track) { @@ -109,18 +109,18 @@ function loadTracks(type, tracksJson) { if (type == 'song') { for (var j = 0; (j < tracksArray.length) && !skip; j++) { // Skip duplicate songs - skip = (track.trackName == tracksArray[j].trackName); + skip = (track.item.trackName == tracksArray[j].trackName); } } else { - skip = (track.albumName != albumName); + skip = (track.item.albumName != albumName); } if (!skip) { tracksArray.push({ - trackName: track.trackName, - artistName: track.artistName, - albumTrackNumber:track.albumTrackNumber, - uri: track.uri, - metadata: track.metadata + trackName: track.item.trackName, + artistName: track.item.artistName, + albumTrackNumber:track.item.albumTrackNumber, + uri: track.item.uri, + metadata: track.item.metadata }); tracks.count++; } @@ -148,7 +148,7 @@ function libIsEmpty() { function loadFuse(items, fuzzyKeys) { return new Promise((resolve) => { - return resolve(new Fuse(items, { keys: fuzzyKeys, threshold: 0.2, distance: 5, maxPatternLength: 100 })); + return resolve(new Fuse(items, { keys: fuzzyKeys, threshold: 0.2, maxPatternLength: 100, ignoreLocation: true })); }); } diff --git a/package-lock.json b/package-lock.json index 8873d5e6..8ce6f9ce 100644 --- a/package-lock.json +++ b/package-lock.json @@ -818,9 +818,9 @@ "dev": true }, "fuse.js": { - "version": "2.7.4", - "resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-2.7.4.tgz", - "integrity": "sha1-luQg/efvARrEnCWKYhMU/ldlNvk=" + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-6.4.1.tgz", + "integrity": "sha512-+hAS7KYgLXontDh/vqffs7wIBw0ceb9Sx8ywZQhOsiQGcSO5zInGhttWOUYQYlvV/yYMJOacQ129Xs3mP3+oZQ==" }, "getpass": { "version": "0.1.7", diff --git a/package.json b/package.json index af9afe88..c19a4db9 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "anesidora": "^1.2.0", "aws-sdk": "^2.12.0", "basic-auth": "~1.1.0", - "fuse.js": "^2.5.0", + "fuse.js": "^6.4.1", "html-entities": "^1.2.1", "json5": "^0.5.1", "mime": "^1.4.1", From 07b5e12143e7f6a06d7b524e5a6b37cff40e7af3 Mon Sep 17 00:00:00 2001 From: Jimmy Shimizu Date: Mon, 8 Feb 2021 16:33:17 +0100 Subject: [PATCH 094/123] Update index.html --- static/index.html | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/static/index.html b/static/index.html index fc16b3d4..7bfc06db 100644 --- a/static/index.html +++ b/static/index.html @@ -95,9 +95,11 @@

    Queue

    Room Grouping

      -
    • add/{other zone name}
    • -
    • remove/{other zone name}
    • +
    • add/{player name from existing zone, prefix with player you want to join}
    • +
    • join/{player player to join in, prefix with player from current zone} (this is just the inverse of add)
    • isolate
    • +
    • ungroup (alias of isolate)
    • +
    • leave (alias of isolate)

    Other

    From 545fd3d21fd11519cee1797727b5f2a0ca868e92 Mon Sep 17 00:00:00 2001 From: Michel Bardelmeijer Date: Mon, 18 Oct 2021 12:54:46 +0200 Subject: [PATCH 095/123] Add note about household value --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 997f6a59..9302c616 100644 --- a/README.md +++ b/README.md @@ -325,7 +325,7 @@ Available options are: * auth: require basic auth credentials which requires a username and password * announceVolume: the percentual volume use when invoking say/sayall without any volume parameter * presetDir: absolute path to look for presets (folder must exist!) -* household: when theres multiple sonos accounts on one network (example: Sonos_ab7d67898dcc5a6d, find it in [Your sonos IP]:1400/status/zp) +* household: when theres multiple sonos accounts on one network (example: Sonos_ab7d67898dcc5a6d, find it in [Your sonos IP]:1400/status/zp). Note that the value after the '.' should not be removed. See more info here: https://github.com/jishi/node-sonos-http-api/issues/783 Example: From 01f60ff08080158ed0ef4db55d2fc97306b30244 Mon Sep 17 00:00:00 2001 From: Mike Beattie Date: Tue, 8 Mar 2022 16:11:39 +1300 Subject: [PATCH 096/123] Add toggles for on/off actions Closes: #813 Signed-off-by: Mike Beattie --- README.md | 10 +++++----- lib/actions/equalizer.js | 6 ++++-- lib/actions/playmode.js | 14 ++++++++++++-- 3 files changed, 21 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 9302c616..6e2aaff7 100644 --- a/README.md +++ b/README.md @@ -89,9 +89,9 @@ The actions supported as of today: * favorites (with optional "detailed" parameter) * playlist * lockvolumes / unlockvolumes (experimental, will enforce the volume that was selected when locking!) -* repeat (on/off) -* shuffle (on/off) -* crossfade (on/off) +* repeat (on(=all)/one/off(=none)/toggle) +* shuffle (on/off/toggle) +* crossfade (on/off/toggle) * pauseall (with optional timeout in minutes) * resumeall (will resume the ones that was pause on the pauseall call. Useful for doorbell, phone calls, etc. Optional timeout) * say @@ -106,8 +106,8 @@ The actions supported as of today: * clippreset * join / leave (Grouping actions) * sub (on/off/gain/crossover/polarity) See SUB section for more info -* nightmode (on/off, PLAYBAR only) -* speechenhancement (on/off, PLAYBAR only) +* nightmode (on/off/toggle, PLAYBAR only) +* speechenhancement (on/off/toggle, PLAYBAR only) * bass/treble (use -10 thru 10 as value. 0 is neutral) diff --git a/lib/actions/equalizer.js b/lib/actions/equalizer.js index beba3655..d4ad5b8d 100644 --- a/lib/actions/equalizer.js +++ b/lib/actions/equalizer.js @@ -1,12 +1,14 @@ 'use strict'; function nightMode(player, values) { - const enable = values[0] === 'on'; + let enable = values[0] === 'on'; + if(values[0] == "toggle") enable = !player.coordinator.state.equalizer.nightMode; return player.nightMode(enable); } function speechEnhancement(player, values) { - const enable = values[0] === 'on'; + let enable = values[0] === 'on'; + if(values[0] == "toggle") enable = !player.coordinator.state.equalizer.speechEnhancement; return player.speechEnhancement(enable); } diff --git a/lib/actions/playmode.js b/lib/actions/playmode.js index ff13d88e..b8d21c9e 100644 --- a/lib/actions/playmode.js +++ b/lib/actions/playmode.js @@ -6,17 +6,27 @@ function repeat(player, values) { mode = "all"; } else if (mode === "off") { mode = "none"; + } else if (mode === "toggle") { + switch (player.coordinator.state.playMode.repeat) { + case 'all': mode = "one"; break; + case 'one': mode = "off"; break; + default: mode = "all"; + } } return player.coordinator.repeat(mode); } function shuffle(player, values) { - return player.coordinator.shuffle(values[0] == "on" ? true : false); + let enable = values[0] === "on"; + if(values[0] == "toggle") enable = !player.coordinator.state.playMode.shuffle; + return player.coordinator.shuffle(enable); } function crossfade(player, values) { - return player.coordinator.crossfade(values[0] == "on" ? true : false); + let enable = values[0] === "on"; + if(values[0] == "toggle") enable = !player.coordinator.state.playMode.crossfade; + return player.coordinator.crossfade(enable); } module.exports = function (api) { From 03533697324d10f721393954354550bd43dd842f Mon Sep 17 00:00:00 2001 From: Mike Beattie Date: Tue, 8 Mar 2022 16:15:36 +1300 Subject: [PATCH 097/123] Add state of toggled setting to action responses Signed-off-by: Mike Beattie --- lib/actions/equalizer.js | 14 ++++++++++---- lib/actions/mute.js | 11 +++++++++-- lib/actions/playmode.js | 20 +++++++++++++++----- lib/actions/playpause.js | 11 +++++++++-- 4 files changed, 43 insertions(+), 13 deletions(-) diff --git a/lib/actions/equalizer.js b/lib/actions/equalizer.js index d4ad5b8d..51c1c3da 100644 --- a/lib/actions/equalizer.js +++ b/lib/actions/equalizer.js @@ -2,14 +2,20 @@ function nightMode(player, values) { let enable = values[0] === 'on'; - if(values[0] == "toggle") enable = !player.coordinator.state.equalizer.nightMode; - return player.nightMode(enable); + let ret = { status: 'success' }; + if(values[0] == "toggle") enable = ret.nightmode = !player.coordinator.state.equalizer.nightMode; + return player.nightMode(enable).then((response) => { + return (!response || response.constructor.name === 'IncomingMessage') ? ret : response; + }); } function speechEnhancement(player, values) { let enable = values[0] === 'on'; - if(values[0] == "toggle") enable = !player.coordinator.state.equalizer.speechEnhancement; - return player.speechEnhancement(enable); + let ret = { status: 'success' }; + if(values[0] == "toggle") enable = ret.speechenhancement = !player.coordinator.state.equalizer.speechEnhancement; + return player.speechEnhancement(enable).then((response) => { + return (!response || response.constructor.name === 'IncomingMessage') ? ret : response; + }); } function bass(player, values) { diff --git a/lib/actions/mute.js b/lib/actions/mute.js index efa6c938..1e494df6 100644 --- a/lib/actions/mute.js +++ b/lib/actions/mute.js @@ -16,11 +16,18 @@ function groupUnmute(player) { } function toggleMute(player) { + let ret = { status: 'success', muted: true }; + if(player.state.mute) { - return player.unMute(); + ret.muted = false; + return player.unMute().then((response) => { + return (!response || response.constructor.name === 'IncomingMessage') ? ret : response; + }); }; - return player.mute(); + return player.mute().then((response) => { + return (!response || response.constructor.name === 'IncomingMessage') ? ret : response; + }); } module.exports = function (api) { diff --git a/lib/actions/playmode.js b/lib/actions/playmode.js index b8d21c9e..2714a900 100644 --- a/lib/actions/playmode.js +++ b/lib/actions/playmode.js @@ -1,6 +1,7 @@ 'use strict'; function repeat(player, values) { let mode = values[0]; + let ret = { status: 'success' }; if (mode === "on") { mode = "all"; @@ -12,21 +13,30 @@ function repeat(player, values) { case 'one': mode = "off"; break; default: mode = "all"; } + ret.repeat = mode; } - return player.coordinator.repeat(mode); + return player.coordinator.repeat(mode).then((response) => { + return (!response || response.constructor.name === 'IncomingMessage') ? ret : response; + }); } function shuffle(player, values) { let enable = values[0] === "on"; - if(values[0] == "toggle") enable = !player.coordinator.state.playMode.shuffle; - return player.coordinator.shuffle(enable); + let ret = { status: 'success' }; + if(values[0] == "toggle") enable = ret.shuffle = !player.coordinator.state.playMode.shuffle; + return player.coordinator.shuffle(enable).then((response) => { + return (!response || response.constructor.name === 'IncomingMessage') ? ret : response; + }); } function crossfade(player, values) { let enable = values[0] === "on"; - if(values[0] == "toggle") enable = !player.coordinator.state.playMode.crossfade; - return player.coordinator.crossfade(enable); + let ret = { status: 'success' }; + if(values[0] == "toggle") enable = ret.crossfade = !player.coordinator.state.playMode.crossfade; + return player.coordinator.crossfade(enable).then((response) => { + return (!response || response.constructor.name === 'IncomingMessage') ? ret : response; + }); } module.exports = function (api) { diff --git a/lib/actions/playpause.js b/lib/actions/playpause.js index 8732b97b..aac63c9e 100644 --- a/lib/actions/playpause.js +++ b/lib/actions/playpause.js @@ -1,10 +1,17 @@ 'use strict'; function playpause(player) { + let ret = { status: 'success', paused: false }; + if(player.coordinator.state.playbackState === 'PLAYING') { - return player.coordinator.pause(); + ret.paused = true; + return player.coordinator.pause().then((response) => { + return (!response || response.constructor.name === 'IncomingMessage') ? ret : response; + }); } - return player.coordinator.play(); + return player.coordinator.play().then((response) => { + return (!response || response.constructor.name === 'IncomingMessage') ? ret : response; + }); } function play(player) { From f1ac98c41853c3831a6489965d1cece024b6c103 Mon Sep 17 00:00:00 2001 From: Mike Beattie Date: Wed, 9 Mar 2022 11:02:17 +1300 Subject: [PATCH 098/123] Return state for on/off actions as well. Also remove check for IncomingMessage response Signed-off-by: Mike Beattie --- lib/actions/equalizer.js | 10 ++++------ lib/actions/mute.js | 8 ++------ lib/actions/playmode.js | 14 +++++--------- lib/actions/playpause.js | 8 ++------ 4 files changed, 13 insertions(+), 27 deletions(-) diff --git a/lib/actions/equalizer.js b/lib/actions/equalizer.js index 51c1c3da..66c50fd0 100644 --- a/lib/actions/equalizer.js +++ b/lib/actions/equalizer.js @@ -2,19 +2,17 @@ function nightMode(player, values) { let enable = values[0] === 'on'; - let ret = { status: 'success' }; - if(values[0] == "toggle") enable = ret.nightmode = !player.coordinator.state.equalizer.nightMode; + if(values[0] == "toggle") enable = !player.coordinator.state.equalizer.nightMode; return player.nightMode(enable).then((response) => { - return (!response || response.constructor.name === 'IncomingMessage') ? ret : response; + return { status: 'success', nightmode: enable }; }); } function speechEnhancement(player, values) { let enable = values[0] === 'on'; - let ret = { status: 'success' }; - if(values[0] == "toggle") enable = ret.speechenhancement = !player.coordinator.state.equalizer.speechEnhancement; + if(values[0] == "toggle") enable = !player.coordinator.state.equalizer.speechEnhancement; return player.speechEnhancement(enable).then((response) => { - return (!response || response.constructor.name === 'IncomingMessage') ? ret : response; + return { status: 'success', speechenhancement: enable }; }); } diff --git a/lib/actions/mute.js b/lib/actions/mute.js index 1e494df6..6ac2a76e 100644 --- a/lib/actions/mute.js +++ b/lib/actions/mute.js @@ -20,14 +20,10 @@ function toggleMute(player) { if(player.state.mute) { ret.muted = false; - return player.unMute().then((response) => { - return (!response || response.constructor.name === 'IncomingMessage') ? ret : response; - }); + return player.unMute().then((response) => { return ret; }); }; - return player.mute().then((response) => { - return (!response || response.constructor.name === 'IncomingMessage') ? ret : response; - }); + return player.mute().then((response) => { return ret; }); } module.exports = function (api) { diff --git a/lib/actions/playmode.js b/lib/actions/playmode.js index 2714a900..2746beee 100644 --- a/lib/actions/playmode.js +++ b/lib/actions/playmode.js @@ -1,7 +1,6 @@ 'use strict'; function repeat(player, values) { let mode = values[0]; - let ret = { status: 'success' }; if (mode === "on") { mode = "all"; @@ -13,29 +12,26 @@ function repeat(player, values) { case 'one': mode = "off"; break; default: mode = "all"; } - ret.repeat = mode; } return player.coordinator.repeat(mode).then((response) => { - return (!response || response.constructor.name === 'IncomingMessage') ? ret : response; + return { status: 'success', repeat: mode }; }); } function shuffle(player, values) { let enable = values[0] === "on"; - let ret = { status: 'success' }; - if(values[0] == "toggle") enable = ret.shuffle = !player.coordinator.state.playMode.shuffle; + if(values[0] == "toggle") enable = !player.coordinator.state.playMode.shuffle; return player.coordinator.shuffle(enable).then((response) => { - return (!response || response.constructor.name === 'IncomingMessage') ? ret : response; + return { status: 'success', shuffle: mode }; }); } function crossfade(player, values) { let enable = values[0] === "on"; - let ret = { status: 'success' }; - if(values[0] == "toggle") enable = ret.crossfade = !player.coordinator.state.playMode.crossfade; + if(values[0] == "toggle") enable = !player.coordinator.state.playMode.crossfade; return player.coordinator.crossfade(enable).then((response) => { - return (!response || response.constructor.name === 'IncomingMessage') ? ret : response; + return { status: 'success', crossfade: enable }; }); } diff --git a/lib/actions/playpause.js b/lib/actions/playpause.js index aac63c9e..655c0544 100644 --- a/lib/actions/playpause.js +++ b/lib/actions/playpause.js @@ -4,14 +4,10 @@ function playpause(player) { if(player.coordinator.state.playbackState === 'PLAYING') { ret.paused = true; - return player.coordinator.pause().then((response) => { - return (!response || response.constructor.name === 'IncomingMessage') ? ret : response; - }); + return player.coordinator.pause().then((response) => { return ret; }); } - return player.coordinator.play().then((response) => { - return (!response || response.constructor.name === 'IncomingMessage') ? ret : response; - }); + return player.coordinator.play().then((response) => { return ret; }); } function play(player) { From ce064fa7d93afa430166f11fa26c8ced7dcd9a83 Mon Sep 17 00:00:00 2001 From: Tom Slominski Date: Thu, 10 Mar 2022 19:06:20 +0000 Subject: [PATCH 099/123] Add action for the BBC Sounds service --- lib/actions/bbcSounds.js | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 lib/actions/bbcSounds.js diff --git a/lib/actions/bbcSounds.js b/lib/actions/bbcSounds.js new file mode 100644 index 00000000..cb4d63c9 --- /dev/null +++ b/lib/actions/bbcSounds.js @@ -0,0 +1,38 @@ +'use strict'; + +function getMetadata(station) { + return ` + BBC Soundsobject.item.audioItem.audioBroadcast + SA_RINCON83207_`; +} + +function getUri(station) { + return `x-sonosapi-hls:stations%7eplayable%7e%7e${station}%7e%7eurn%3abbc%3aradio%3anetwork%3a${station}?sid=325&flags=288&sn=10`; +} + +/** + * @link https://gist.github.com/bpsib/67089b959e4fa898af69fea59ad74bc3 Stream names can be found here + */ +function bbcSounds(player, values) { + const action = values[0]; + const station = encodeURIComponent(values[1]); + + if (!station) { + return Promise.reject('Expected BBC Sounds station name.'); + } + + const metadata = getMetadata(station); + const uri = getUri(station); + + if (action === 'play') { + return player.coordinator.setAVTransport(uri, metadata).then(() => player.coordinator.play()); + } else if (action === 'set') { + return player.coordinator.setAVTransport(uri, metadata); + } + + return Promise.reject('BBC Sounds only handles the {play} & {set} actions.'); +} + +module.exports = function (api) { + api.registerAction('bbcsounds', bbcSounds); +} From 025bbaba76d69e2cf9b6e1b233025efd36115400 Mon Sep 17 00:00:00 2001 From: jsiegenthaler <59387202+jsiegenthaler@users.noreply.github.com> Date: Mon, 14 Mar 2022 20:47:45 +0100 Subject: [PATCH 100/123] Update README.md Updated Readme to make Sonos S2 update more time-relevant, as S2 was released in June 2020 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6e2aaff7..121479c8 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ⚠WARNING!⚠ -The upcoming Sonos software update (dubbed S2) seems to still work. However, it might break in the future if and when they decide to drop UPnP as control protocol. +The Sonos S2 update, released June 2020, still works with this API. However, it might break in the future if and when Sonos decide to drop UPnP as the control protocol. Feel free to use it as you please. Consider donating if you want to support further development. Reach out on the gitter chat if you have issues getting it to run, instead of creating new issues, thank you! From 00efb653a6e22e13adfe62c4e7788aa63696a7d1 Mon Sep 17 00:00:00 2001 From: jsiegenthaler <59387202+jsiegenthaler@users.noreply.github.com> Date: Mon, 14 Mar 2022 20:57:08 +0100 Subject: [PATCH 101/123] Update README.md Fixed small grammar error --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 121479c8..3c8bb489 100644 --- a/README.md +++ b/README.md @@ -610,7 +610,7 @@ To get a current list of voices, you would need to use the AWS CLI and invoke th #### Google (default if no other has been configured) -Does not require any API keys. Please note that Google has been known in the past to change the requirements for its Text-to-Speech API, and this may stop working in the future. There is also limiations to have many request one is allowed to do in a specific time period. +Does not require any API keys. Please note that Google has been known in the past to change the requirements for its Text-to-Speech API, and this may stop working in the future. There is also limiations to how many requests one is allowed to do in a specific time period. The following language codes are supported From 2a22f8a6de021af3380c979320d82f19de2cdfaa Mon Sep 17 00:00:00 2001 From: ashenshugarRET <42817877+ashenshugarRET@users.noreply.github.com> Date: Mon, 14 Mar 2022 20:24:19 +0000 Subject: [PATCH 102/123] Adding instruction for BBC Sounds Service (#826) * Update README.md to include BBC Sounds Instructions Update README.md to include BBC Sounds Instructions * Update README.md Completion of documentation for BBC Sounds service * Create bbSounds.js Copying file from @tomslominski pull request to add BBC Sounds to @jishi/node-sonos-http-api * Rename bbSounds.js to bbcSounds.js * Update README.md Correction to stream names and available streams * Delete bbcSounds.js --- README.md | 78 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/README.md b/README.md index 3c8bb489..a020d082 100644 --- a/README.md +++ b/README.md @@ -851,6 +851,84 @@ The format is: https://music.amazon.de/albums/{albumID}?trackAsin={songID}&ref=d The format is: https://music.amazon.de/albums/{albumID}?ref=dm_sh_97aa-255b-dmcp-c6ba-4ff00&musicTerritory=DE&marketplaceId=A1PA6795UKMFR9 > eg: https://music.amazon.de/albums/B0727SH7LW?ref=dm_sh_97aa-255b-dmcp-c6ba-4ff00&musicTerritory=DE&marketplaceId=A1PA6795UKMFR9 +BBC Sounds +---------------------- +You can specify a BBC station and the station will be played or set depending on the command used. + +To play immediately: +``` +/RoomName/bbcsounds/play/{stream code} +``` +To set the station without playing: +``` +/RoomName/bbcsounds/set/{stream code} +``` + +Refer to the table below for available codes for BBC Radio Stations + +| BBC Radio Station Name | Stream Code | +|----------------------------------|----------------------------------| +| BBC Radio 1 | bbc_radio_one | +| BBC 1Xtra | bbc_1xtra | +| BBC 1Dance | bbc_1dance | +| BBC 1Relax | bbc_1relax | +| BBC Radio 2 | bbc_radio_two | +| BBC Radio 3 | bbc_radio_three | +| BBC Radio 4 | bbc_radio_four | +| BBC Radio 4 Extra | bbc_radio_four_extra | +| BBC Radio 5 Live | bbc_radio_five_live | +| BBC Radio 5 Live Sports Extra | bbc_five_live_sports_extra | +| BBC Radio 6 Music | bbc_6music | +| BBC Asian Network | bbc_asian_network | +| BBC Radio Berkshire | bbc_radio_berkshire | +| BBC Radio Bristol | bbc_radio_bristol | +| BBC Radio Cambridge | bbc_radio_cambridge | +| BBC Radio Cornwall | bbc_radio_cornwall | +| BBC Radio Cumbria | bbc_radio_cumbria | +| BBC Radio Cymru | bbc_radio_cymru | +| BBC Radio Cymru 2 | bbc_radio_cymru_2 | +| BBC Radio CWR | bbc_radio_coventry_warwickshire | +| BBC Radio Derby | bbc_radio_derby | +| BBC Radio Devon | bbc_radio_devon | +| BBC Radio Essex | bbc_radio_essex | +| BBC Radio Foyle | bbc_radio_foyle | +| BBC Radio Gloucestershire | bbc_radio_gloucestershire | +| BBC Radio Guernsey | bbc_radio_guernsey | +| BBC Radio Hereford Worcester | bbc_radio_hereford_worcester | +| BBC Radio Humberside | bbc_radio_humberside | +| BBC Radio Jersey | bbc_radio_jersey | +| BBC Radio Kent | bbc_radio_kent | +| BBC Radio Lancashire | bbc_radio_lancashire | +| BBC Radio Leeds | bbc_radio_leeds | +| BBC Radio Leicester | bbc_radio_leicester | +| BBC Radio Lincolnshire | bbc_radio_lincolnshire | +| BBC Radio London | bbc_london | +| BBC Radio Manchester | bbc_radio_manchester | +| BBC Radio Merseyside | bbc_radio_merseyside | +| BBC Radio nan Gaidheal | bbc_radio_nan_gaidheal | +| BBC Radio Newcastle | bbc_radio_newcastle | +| BBC Radio Norfolk | bc_radio_norfolk | +| BBC Radio Northampton | bbc_radio_northampton | +| BBC Radio Nottingham | bbc_radio_nottingham | +| BBC Radio Oxford | bbc_radio_oxford | +| BBC Radio Scotland FM | bbc_radio_scotland_fm | +| BBC Radio Sheffield | bbc_radio_sheffield | +| BBC Radio Shropshire | bbc_radio_shropshire | +| BBC Radio Solent | bbc_radio_solent | +| BBC Radio Somerset | bbc_radio_somerset_sound | +| BBC Radio Stoke | bbc_radio_stoke | +| BBC Radio Suffolk | bbc_radio_suffolk | +| BBC Radio Surrey | bbc_radio_surrey | +| BBC Radio Sussex | bbc_radio_sussex | +| BBC Radio Tees | bbc_tees | +| BBC Radio Three Counties Radio | bbc_three_counties_radio | +| BBC Radio Ulster | bbc_radio_ulster | +| BBC Radio Wales | bbc_radio_wales_fm | +| BBC Radio Wiltshire | bbc_radio_wiltshire | +| BBC Radio WM | bbc_wm | +| BBC Radio York | bbc_radio_york | +| BBC World_Service | bbc_world_service | +| Cbeebies Radio | cbeebies_radio | SiriusXM ---------------------- From 44096ce9103e30c58832bf4bdf52f2bcb11fd676 Mon Sep 17 00:00:00 2001 From: ashenshugarRET <42817877+ashenshugarRET@users.noreply.github.com> Date: Wed, 30 Mar 2022 07:59:05 +0100 Subject: [PATCH 103/123] Update to README.md to clarify services need to be installed Add clarity that the services need to be installed in the SONOS account for node-sonos-http-api to be able control them. Added clarity that the BBC Sounds service is only available in the UK --- README.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a020d082..4ad12503 100644 --- a/README.md +++ b/README.md @@ -786,7 +786,9 @@ Switch "placement adjustment" or more commonly known as phase. 0 = 0°, 1 = 180 Spotify, Apple Music and Amazon Music (Experimental) ---------------------- -Allows you to perform your own external searches for Apple Music or Spotify songs or albums and play a specified song or track ID. The Music Search funtionality outlined further below performs a search of its own and plays the specified music. +Allows you to perform your own external searches for Spotify, Apple Music or Amazon Music songs or albums and play a specified song or track ID. The Music Search funtionality outlined further below performs a search of its own and plays the specified music. + +Ensure you have added and registered the respective service with your Sonos account, before trying to control your speakers with node-sonos-http-api. Instructions on how to do this can be found here: https://support.sonos.com/s/article/2757?language=en_US The following endpoints are available: @@ -851,8 +853,10 @@ The format is: https://music.amazon.de/albums/{albumID}?trackAsin={songID}&ref=d The format is: https://music.amazon.de/albums/{albumID}?ref=dm_sh_97aa-255b-dmcp-c6ba-4ff00&musicTerritory=DE&marketplaceId=A1PA6795UKMFR9 > eg: https://music.amazon.de/albums/B0727SH7LW?ref=dm_sh_97aa-255b-dmcp-c6ba-4ff00&musicTerritory=DE&marketplaceId=A1PA6795UKMFR9 -BBC Sounds +BBC Sounds (as of 2022 only available in the UK) ---------------------- +Ensure you have added and registered the BBC Sounds service with your Sonos account, before trying to control your speakers with node-sonos-http-api. Instructions on how to do this can be found here: https://www.bbc.co.uk/sounds/help/questions/listening-on-a-smart-speaker/sonos or here: https://support.sonos.com/s/article/2757?language=en_US + You can specify a BBC station and the station will be played or set depending on the command used. To play immediately: From 77420b4f4fae1c229045b9db902f6fc2e567f23a Mon Sep 17 00:00:00 2001 From: Jim Lamb Date: Wed, 9 Nov 2022 09:27:06 +0000 Subject: [PATCH 104/123] Correct BBC Sounds stream code for BBC Radio 4, creating FM and LW variations --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 4ad12503..0ed74289 100644 --- a/README.md +++ b/README.md @@ -878,7 +878,8 @@ Refer to the table below for available codes for BBC Radio Stations | BBC 1Relax | bbc_1relax | | BBC Radio 2 | bbc_radio_two | | BBC Radio 3 | bbc_radio_three | -| BBC Radio 4 | bbc_radio_four | +| BBC Radio 4 FM | bbc_radio_fourfm | +| BBC Radio 4 LW | bbc_radio_fourlw | | BBC Radio 4 Extra | bbc_radio_four_extra | | BBC Radio 5 Live | bbc_radio_five_live | | BBC Radio 5 Live Sports Extra | bbc_five_live_sports_extra | From 8e6f57992ecc8035472085d566a0356d4c4d6b87 Mon Sep 17 00:00:00 2001 From: jsiegenthaler <59387202+jsiegenthaler@users.noreply.github.com> Date: Tue, 5 Apr 2022 07:42:12 +0200 Subject: [PATCH 105/123] Update README.md Fix spelling mistake, improve readability --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0ed74289..d950bb4c 100644 --- a/README.md +++ b/README.md @@ -108,7 +108,7 @@ The actions supported as of today: * sub (on/off/gain/crossover/polarity) See SUB section for more info * nightmode (on/off/toggle, PLAYBAR only) * speechenhancement (on/off/toggle, PLAYBAR only) -* bass/treble (use -10 thru 10 as value. 0 is neutral) +* bass/treble (use -10 through to 10 as the value. 0 is neutral) State From 61790dbd7e6ea114844d2ab430a7460851616e49 Mon Sep 17 00:00:00 2001 From: ashenshugarRET <42817877+ashenshugarRET@users.noreply.github.com> Date: Fri, 20 Jan 2023 19:12:24 +0000 Subject: [PATCH 106/123] Update BBC Sounds Station references Updating BBC Sounds Station references in the README.md --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d950bb4c..238b3c71 100644 --- a/README.md +++ b/README.md @@ -874,8 +874,8 @@ Refer to the table below for available codes for BBC Radio Stations |----------------------------------|----------------------------------| | BBC Radio 1 | bbc_radio_one | | BBC 1Xtra | bbc_1xtra | -| BBC 1Dance | bbc_1dance | -| BBC 1Relax | bbc_1relax | +| BBC 1Dance | bbc_radio_one_dance | +| BBC 1Relax | bbc_radio_one_relax | | BBC Radio 2 | bbc_radio_two | | BBC Radio 3 | bbc_radio_three | | BBC Radio 4 FM | bbc_radio_fourfm | @@ -917,6 +917,7 @@ Refer to the table below for available codes for BBC Radio Stations | BBC Radio Nottingham | bbc_radio_nottingham | | BBC Radio Oxford | bbc_radio_oxford | | BBC Radio Scotland FM | bbc_radio_scotland_fm | +| BBC Radio Scotland Extra | bbc_radio_scotland_mw | | BBC Radio Sheffield | bbc_radio_sheffield | | BBC Radio Shropshire | bbc_radio_shropshire | | BBC Radio Solent | bbc_radio_solent | @@ -929,6 +930,7 @@ Refer to the table below for available codes for BBC Radio Stations | BBC Radio Three Counties Radio | bbc_three_counties_radio | | BBC Radio Ulster | bbc_radio_ulster | | BBC Radio Wales | bbc_radio_wales_fm | +| BBC Radio Wales Extra | bbc_radio_wales_am | | BBC Radio Wiltshire | bbc_radio_wiltshire | | BBC Radio WM | bbc_wm | | BBC Radio York | bbc_radio_york | From 5d9e5354374bb789daef02fd4d0f2ba88bd6b79e Mon Sep 17 00:00:00 2001 From: Tim Otto Date: Mon, 27 Feb 2023 20:22:25 +0100 Subject: [PATCH 107/123] Add HTTP event source endpoint --- lib/sonos-http-api.js | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/lib/sonos-http-api.js b/lib/sonos-http-api.js index 3b11c8a4..135d4095 100644 --- a/lib/sonos-http-api.js +++ b/lib/sonos-http-api.js @@ -9,6 +9,7 @@ function HttpAPI(discovery, settings) { const port = settings.port; const webroot = settings.webroot; const actions = {}; + const events = new HttpEventServer(); this.getWebRoot = function () { return webroot; @@ -52,6 +53,11 @@ function HttpAPI(discovery, settings) { return; } + if (req.url === '/events') { + events.addClient(res); + return; + } + if (discovery.zones.length === 0) { const msg = 'No system has yet been discovered. Please see https://github.com/jishi/node-sonos-http-api/issues/77 if it doesn\'t resolve itself in a few seconds.'; logger.error(msg); @@ -122,7 +128,6 @@ function HttpAPI(discovery, settings) { function invokeWebhook(type, data) { var typeName = "type"; var dataName = "data"; - if (!settings.webhook) return; if (settings.webhookType) { typeName = settings.webhookType; } if (settings.webhookData) { dataName = settings.webhookData; } @@ -132,6 +137,10 @@ function HttpAPI(discovery, settings) { [dataName]: data }); + events.sendEvent(jsonBody); + + if (!settings.webhook) return; + const body = new Buffer(jsonBody, 'utf8'); var headers = { @@ -156,4 +165,22 @@ function HttpAPI(discovery, settings) { } +function HttpEventServer() { + let clients = []; + + const removeClient = client => clients = clients.filter(value => value !== client); + + this.addClient = res => clients.push(new HttpEventSource(res, removeClient)); + + this.sendEvent = event => clients.forEach(client => client.sendEvent(event)) +} + +function HttpEventSource(res, done) { + this.sendEvent = event => res.write('data: ' + event + '\n\n') + + res.on('close', () => done(this)) + + res.setHeader('Content-Type', 'text/event-stream'); +} + module.exports = HttpAPI; From 35ccda34178ef63494119fa6527af12a87b89041 Mon Sep 17 00:00:00 2001 From: Tim Otto Date: Sun, 5 Mar 2023 19:53:52 +0100 Subject: [PATCH 108/123] refactoring --- lib/helpers/http-event-server.js | 19 +++++++++++++++++++ lib/sonos-http-api.js | 19 +------------------ 2 files changed, 20 insertions(+), 18 deletions(-) create mode 100644 lib/helpers/http-event-server.js diff --git a/lib/helpers/http-event-server.js b/lib/helpers/http-event-server.js new file mode 100644 index 00000000..56e5d176 --- /dev/null +++ b/lib/helpers/http-event-server.js @@ -0,0 +1,19 @@ +function HttpEventServer() { + let clients = []; + + const removeClient = client => clients = clients.filter(value => value !== client); + + this.addClient = res => clients.push(new HttpEventSource(res, removeClient)); + + this.sendEvent = event => clients.forEach(client => client.sendEvent(event)) +} + +function HttpEventSource(res, done) { + this.sendEvent = event => res.write('data: ' + event + '\n\n') + + res.on('close', () => done(this)) + + res.setHeader('Content-Type', 'text/event-stream'); +} + +module.exports = HttpEventServer; diff --git a/lib/sonos-http-api.js b/lib/sonos-http-api.js index 135d4095..bc38520f 100644 --- a/lib/sonos-http-api.js +++ b/lib/sonos-http-api.js @@ -3,6 +3,7 @@ const requireDir = require('./helpers/require-dir'); const path = require('path'); const request = require('sonos-discovery/lib/helpers/request'); const logger = require('sonos-discovery/lib/helpers/logger'); +const HttpEventServer = require('./helpers/http-event-server'); function HttpAPI(discovery, settings) { @@ -165,22 +166,4 @@ function HttpAPI(discovery, settings) { } -function HttpEventServer() { - let clients = []; - - const removeClient = client => clients = clients.filter(value => value !== client); - - this.addClient = res => clients.push(new HttpEventSource(res, removeClient)); - - this.sendEvent = event => clients.forEach(client => client.sendEvent(event)) -} - -function HttpEventSource(res, done) { - this.sendEvent = event => res.write('data: ' + event + '\n\n') - - res.on('close', () => done(this)) - - res.setHeader('Content-Type', 'text/event-stream'); -} - module.exports = HttpAPI; From aac16a5161ffef5721ed88f11901576bcdca7664 Mon Sep 17 00:00:00 2001 From: Tim Otto Date: Sun, 5 Mar 2023 20:23:40 +0100 Subject: [PATCH 109/123] explain /events endpoint --- README.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/README.md b/README.md index 238b3c71..73ef19eb 100644 --- a/README.md +++ b/README.md @@ -1101,6 +1101,31 @@ or "data" property will be equal to the same data as you would get from /RoomName/state or /zones. There is an example endpoint in the root if this project called test_endpoint.js which you may fire up to get an understanding of what is posted, just invoke it with "node test_endpoint.js" in a terminal, and then start the http-api in another terminal. +Server Sent Events +----- + +As an alternative to the web hook you can also call the `/events` endpoint to receive every state change and topology change as [Server Sent Event](https://html.spec.whatwg.org/multipage/server-sent-events.html#server-sent-events). +Compared to the web hook there is no configuration required on the server, and you can listen for events from multiple clients. + +Because it is a long-polling connection, you must take care of errors in your client code and re-connect if necessary. + +The server sends events formatted as single-line JSON in the format of Server Sent Events: every event starts with the string `data: `, followed by the single-line JSON formatted event, and is terminated by two new line characters. + +There are [several client libraries available](https://en.wikipedia.org/wiki/Server-sent_events#Libraries) to listen for Server Sent Events. +Using `curl` yields the following output for some volume changes: + +```shell +host:~ user$ curl localhost:5005/events +data: {"type":"volume-change","data":{"uuid":"RINCON_E2832F58D9074C45B","previousVolume":13,"newVolume":19,"roomName":"Office"}} + +data: {"type":"volume-change","data":{"uuid":"RINCON_E2832F58D9074C45B","previousVolume":19,"newVolume":25,"roomName":"Office"}} + +data: {"type":"volume-change","data":{"uuid":"RINCON_E2832F58D9074C45B","previousVolume":25,"newVolume":24,"roomName":"Office"}} + +data: {"type":"volume-change","data":{"uuid":"RINCON_E2832F58D9074C45B","previousVolume":23,"newVolume":23,"roomName":"Office"}} + +``` + DOCKER ----- From ec87f092cc720b5f75620f518a18526976d1bcae Mon Sep 17 00:00:00 2001 From: Luke Karrys Date: Sun, 2 Apr 2023 15:42:25 -0700 Subject: [PATCH 110/123] add apple music playlist support --- lib/actions/appleMusic.js | 35 +++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/lib/actions/appleMusic.js b/lib/actions/appleMusic.js index 7099aa8e..015fac90 100644 --- a/lib/actions/appleMusic.js +++ b/lib/actions/appleMusic.js @@ -1,8 +1,9 @@ 'use strict'; + function getMetadata(id, parentUri, type, title) { return ` - "${title}"object.item.audioItem.${type} + "${title}"${type} SA_RINCON52231_X_#Svc52231-0-Token`; } @@ -14,53 +15,63 @@ function getAlbumUri(id) { return `x-rincon-cpcontainer:0004206c${id}`; } +function getPlaylistUri(id) { + return `x-rincon-cpcontainer:1006206c${id}`; +} + const uriTemplates = { song: getSongUri, - album: getAlbumUri + album: getAlbumUri, + playlist: getPlaylistUri, }; const CLASSES = { - song: 'musicTrack', - album: 'musicAlbum' + song: 'object.item.audioItem.musicTrack', + album: 'object.item.audioItem.musicAlbum', + playlist: 'object.container.playlistContainer.#PlaylistView' }; const METADATA_URI_STARTERS = { song: '00032020', - album: '0004206c' + album: '0004206c', + playlist: '1006206c' }; const PARENTS = { song: '0004206calbum%3a', - album: '00020000album%3a' + album: '00020000album%3a', + playlist: '1006206cplaylist%3a' }; function appleMusic(player, values) { const action = values[0]; const trackID = values[1]; const type = trackID.split(':')[0]; - var nextTrackNo = 0; + let nextTrackNo = 0; const metadataID = METADATA_URI_STARTERS[type] + encodeURIComponent(trackID); const metadata = getMetadata(metadataID, PARENTS[type], CLASSES[type], ''); const uri = uriTemplates[type](encodeURIComponent(trackID)); - if (action == 'queue') { + if (action === 'queue') { return player.coordinator.addURIToQueue(uri, metadata); - } else if (action == 'now') { + } else if (action === 'now') { nextTrackNo = player.coordinator.state.trackNo + 1; let promise = Promise.resolve(); if (player.coordinator.avTransportUri.startsWith('x-rincon-queue') === false) { promise = promise.then(() => player.coordinator.setAVTransport(`x-rincon-queue:${player.coordinator.uuid}#0`)); } return promise.then(() => player.coordinator.addURIToQueue(uri, metadata, true, nextTrackNo)) - .then(() => { if (nextTrackNo != 1) player.coordinator.nextTrack() }) + .then(() => { if (nextTrackNo !== 1) player.coordinator.nextTrack(); }) .then(() => player.coordinator.play()); - } else if (action == 'next') { + } else if (action === 'next') { nextTrackNo = player.coordinator.state.trackNo + 1; return player.coordinator.addURIToQueue(uri, metadata, true, nextTrackNo); } + + return null; } -module.exports = function (api) { +module.exports = function appleMusicAction(api) { api.registerAction('applemusic', appleMusic); }; From 7f46c8aa6f04e0bd098f838bac02ee669a5e18fd Mon Sep 17 00:00:00 2001 From: Luke Karrys Date: Thu, 30 Mar 2023 00:10:13 -0700 Subject: [PATCH 111/123] fix: use correct variable for shuffle response --- lib/actions/playmode.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/actions/playmode.js b/lib/actions/playmode.js index 2746beee..7aa2f634 100644 --- a/lib/actions/playmode.js +++ b/lib/actions/playmode.js @@ -23,7 +23,7 @@ function shuffle(player, values) { let enable = values[0] === "on"; if(values[0] == "toggle") enable = !player.coordinator.state.playMode.shuffle; return player.coordinator.shuffle(enable).then((response) => { - return { status: 'success', shuffle: mode }; + return { status: 'success', shuffle: enable }; }); } @@ -39,4 +39,4 @@ module.exports = function (api) { api.registerAction('repeat', repeat); api.registerAction('shuffle', shuffle); api.registerAction('crossfade', crossfade); -} \ No newline at end of file +} From 6637dad7cbe5d0249ca5e7c050865a38e2309523 Mon Sep 17 00:00:00 2001 From: "daniel.schroeder" Date: Sun, 22 Jan 2023 10:41:02 +0100 Subject: [PATCH 112/123] support for AWS polly neural engine --- README.md | 4 +- lib/tts-providers/aws-polly.js | 4 + package-lock.json | 2952 +++++++++++++++++++++++++++++++- package.json | 2 +- 4 files changed, 2923 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index 73ef19eb..b3bacf0e 100644 --- a/README.md +++ b/README.md @@ -531,7 +531,9 @@ You can also specify it for this application only, using: } ``` -Choose the region where you registered your account, or the one closest to you. Polly is only supported in US East (Northern Virginia), US West (Oregon), US East (Ohio), and EU (Ireland) as of today (dec 2016) +To select the neural engine, append `Neural` to the name, e.g. `DanielNeural`. + +Choose the region where you registered your account, or the one closest to you. If you have your credentials elsewhere and want to stick with the default voice, you still need to make sure that the aws config option is set to trigger AWS TTS: diff --git a/lib/tts-providers/aws-polly.js b/lib/tts-providers/aws-polly.js index bd7aa35e..aab95e63 100644 --- a/lib/tts-providers/aws-polly.js +++ b/lib/tts-providers/aws-polly.js @@ -29,6 +29,10 @@ function polly(phrase, voiceName) { if (voiceName) { synthesizeParameters.VoiceId = voiceName; } + if (synthesizeParameters.VoiceId.endsWith('Neural')) { + synthesizeParameters.Engine = 'neural'; + synthesizeParameters.VoiceId = synthesizeParameters.VoiceId.slice(0, -6); + } const phraseHash = crypto.createHash('sha1').update(phrase).digest('hex'); const filename = `polly-${phraseHash}-${synthesizeParameters.VoiceId}.mp3`; diff --git a/package-lock.json b/package-lock.json index 8ce6f9ce..94f21c64 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,8 +1,2776 @@ { "name": "sonos-http-api", "version": "1.6.9", - "lockfileVersion": 1, + "lockfileVersion": 2, "requires": true, + "packages": { + "": { + "name": "sonos-http-api", + "version": "1.6.9", + "license": "MIT", + "dependencies": { + "anesidora": "^1.2.0", + "aws-sdk": "^2.1299.0", + "basic-auth": "~1.1.0", + "fuse.js": "^6.4.1", + "html-entities": "^1.2.1", + "json5": "^0.5.1", + "mime": "^1.4.1", + "music-metadata": "^1.1.0", + "node-static": "^0.7.9", + "request-promise": "~1.0.2", + "sonos-discovery": "https://github.com/jishi/node-sonos-discovery/archive/v1.7.3.tar.gz", + "wav-file-info": "0.0.8" + }, + "devDependencies": { + "eslint": "^4.8.0", + "eslint-config-airbnb-base": "^12.0.1", + "eslint-plugin-import": "^2.7.0" + }, + "engines": { + "node": ">=4.0.0", + "npm": "^2.0.0" + } + }, + "node_modules/acorn": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.1.tgz", + "integrity": "sha512-d+nbxBUGKg7Arpsvbnlq61mc12ek3EY8EQldM3GPAhWJ1UVxC6TDGbIvUMNU6obBX3i1+ptCIzV4vq0gFPEGVQ==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", + "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", + "dev": true, + "dependencies": { + "acorn": "^3.0.4" + } + }, + "node_modules/acorn-jsx/node_modules/acorn": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", + "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ajv": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", + "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "dependencies": { + "co": "^4.6.0", + "fast-deep-equal": "^1.0.0", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.3.0" + } + }, + "node_modules/ajv-keywords": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-2.1.1.tgz", + "integrity": "sha1-YXmX/F9gV2iUxDX5QNgZ4TW4B2I=", + "dev": true, + "peerDependencies": { + "ajv": "^5.0.0" + } + }, + "node_modules/anesidora": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/anesidora/-/anesidora-1.2.1.tgz", + "integrity": "sha1-/ZWdrLiPx6im5xE+OA8/rQmTipo=", + "dependencies": { + "request": "^2.64.0", + "underscore": "^1.8.3" + } + }, + "node_modules/ansi-escapes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz", + "integrity": "sha512-UgAb8H9D41AQnu/PbWlCofQVcnV4Gs2bBJi9eZPxfU/hgglFh3SMDMENRIqdr7H6XFnXdoknctFByVsCOotTVw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "dev": true, + "dependencies": { + "array-uniq": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/asn1": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", + "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=" + }, + "node_modules/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "node_modules/available-typed-arrays": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/aws-sdk": { + "version": "2.1299.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1299.0.tgz", + "integrity": "sha512-xTh6pmCUEJljkFfTM3sE8UozDxal80uX/5WZl8GcjQ+NbrGeQEdvL6wFWBwEEVbhR0VBVuU37cKPuQlfENbRYA==", + "dependencies": { + "buffer": "4.9.2", + "events": "1.1.1", + "ieee754": "1.1.13", + "jmespath": "0.16.0", + "querystring": "0.2.0", + "sax": "1.2.1", + "url": "0.10.3", + "util": "^0.12.4", + "uuid": "8.0.0", + "xml2js": "0.4.19" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/aws-sdk/node_modules/uuid": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.0.0.tgz", + "integrity": "sha512-jOXGuXZAWdsTH7eZLtyXMqUb9EcWMGZNbL9YcGBJl4MH4nrxHmZJhEHvyLFrkxo+28uLb/NYRcStH48fnD0Vzw==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", + "engines": { + "node": "*" + } + }, + "node_modules/aws4": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.7.0.tgz", + "integrity": "sha512-32NDda82rhwD9/JBCCkB+MRYDp0oSvlo2IL6rQWA10PQi7tDUM3eqMSltXmY+Oyl/7N3P3qNtAlv7X0d9bI28w==" + }, + "node_modules/babel-code-frame": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", + "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", + "dev": true, + "dependencies": { + "chalk": "^1.1.3", + "esutils": "^2.0.2", + "js-tokens": "^3.0.2" + } + }, + "node_modules/babel-code-frame/node_modules/chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "dependencies": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/babel-code-frame/node_modules/strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/basic-auth": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-1.1.0.tgz", + "integrity": "sha1-RSIe5Cn37h5QNb4/UVM/HN/SmIQ=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "optional": true, + "dependencies": { + "tweetnacl": "^0.14.3" + } + }, + "node_modules/bluebird": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-2.11.0.tgz", + "integrity": "sha1-U0uQM8AiyVecVro7Plpcqvu2UOE=" + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/buffer": { + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", + "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", + "dependencies": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" + } + }, + "node_modules/buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "node_modules/builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/caller-path": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", + "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", + "dev": true, + "dependencies": { + "callsites": "^0.2.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/callsites": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", + "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + }, + "node_modules/chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chalk/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chardet": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz", + "integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=", + "dev": true + }, + "node_modules/circular-json": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", + "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", + "deprecated": "CircularJSON is in maintenance only, flatted is its successor.", + "dev": true + }, + "node_modules/cli-cursor": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", + "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "dev": true, + "dependencies": { + "restore-cursor": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cli-width": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", + "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", + "dev": true + }, + "node_modules/cls-bluebird": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/cls-bluebird/-/cls-bluebird-1.1.3.tgz", + "integrity": "sha1-syY8EaCJsDlhhaG3q5BNkPAq1Cg=", + "dependencies": { + "is-bluebird": "^1.0.1", + "shimmer": "^1.1.0" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/color-convert": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.2.tgz", + "integrity": "sha512-3NUJZdhMhcdPn8vJ9v2UQJoH0qqoGUkYTgFEPZaPjEtwmmKUfNV46zZmgB2M5M4DCEQHMaCfWHCxiBflLm04Tg==", + "dev": true, + "dependencies": { + "color-name": "1.1.1" + } + }, + "node_modules/color-name": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.1.tgz", + "integrity": "sha1-SxQVMEz1ACjqgWQ2Q72C6gWANok=", + "dev": true + }, + "node_modules/colors": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.3.1.tgz", + "integrity": "sha512-jg/vxRmv430jixZrC+La5kMbUWqIg32/JsYNZb94+JEmzceYbWKTsv1OuTp+7EaqiaWRR2tPcykibwCRgclIsw==", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/combined-stream": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", + "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "node_modules/concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "dev": true, + "engines": [ + "node >= 0.8" + ], + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "node_modules/contains-path": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz", + "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "node_modules/cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "dev": true, + "dependencies": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "node_modules/dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dependencies": { + "assert-plus": "^1.0.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "node_modules/del": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", + "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", + "dev": true, + "dependencies": { + "globby": "^5.0.0", + "is-path-cwd": "^1.0.0", + "is-path-in-cwd": "^1.0.0", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "rimraf": "^2.2.8" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "optional": true, + "dependencies": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/eslint": { + "version": "4.19.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-4.19.1.tgz", + "integrity": "sha512-bT3/1x1EbZB7phzYu7vCr1v3ONuzDtX8WjuM9c0iYxe+cq+pwcKEoQjl7zd3RpC6YOLgnSy3cTN58M2jcoPDIQ==", + "dev": true, + "dependencies": { + "ajv": "^5.3.0", + "babel-code-frame": "^6.22.0", + "chalk": "^2.1.0", + "concat-stream": "^1.6.0", + "cross-spawn": "^5.1.0", + "debug": "^3.1.0", + "doctrine": "^2.1.0", + "eslint-scope": "^3.7.1", + "eslint-visitor-keys": "^1.0.0", + "espree": "^3.5.4", + "esquery": "^1.0.0", + "esutils": "^2.0.2", + "file-entry-cache": "^2.0.0", + "functional-red-black-tree": "^1.0.1", + "glob": "^7.1.2", + "globals": "^11.0.1", + "ignore": "^3.3.3", + "imurmurhash": "^0.1.4", + "inquirer": "^3.0.6", + "is-resolvable": "^1.0.0", + "js-yaml": "^3.9.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.3.0", + "lodash": "^4.17.4", + "minimatch": "^3.0.2", + "mkdirp": "^0.5.1", + "natural-compare": "^1.4.0", + "optionator": "^0.8.2", + "path-is-inside": "^1.0.2", + "pluralize": "^7.0.0", + "progress": "^2.0.0", + "regexpp": "^1.0.1", + "require-uncached": "^1.0.3", + "semver": "^5.3.0", + "strip-ansi": "^4.0.0", + "strip-json-comments": "~2.0.1", + "table": "4.0.2", + "text-table": "~0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-config-airbnb-base": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-12.1.0.tgz", + "integrity": "sha512-/vjm0Px5ZCpmJqnjIzcFb9TKZrKWz0gnuG/7Gfkt0Db1ELJR51xkZth+t14rYdqWgX836XbuxtArbIHlVhbLBA==", + "dev": true, + "dependencies": { + "eslint-restricted-globals": "^0.1.1" + }, + "engines": { + "node": ">= 4" + }, + "peerDependencies": { + "eslint": "^4.9.0", + "eslint-plugin-import": "^2.7.0" + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.2.tgz", + "integrity": "sha512-sfmTqJfPSizWu4aymbPr4Iidp5yKm8yDkHp+Ir3YiTHiiDfxh69mOUsmiqW6RZ9zRXFaF64GtYmN7e+8GHBv6Q==", + "dev": true, + "dependencies": { + "debug": "^2.6.9", + "resolve": "^1.5.0" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/eslint-module-utils": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.2.0.tgz", + "integrity": "sha1-snA2LNiLGkitMIl2zn+lTphBF0Y=", + "dev": true, + "dependencies": { + "debug": "^2.6.8", + "pkg-dir": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.13.0.tgz", + "integrity": "sha512-t6hGKQDMIt9N8R7vLepsYXgDfeuhp6ZJSgtrLEDxonpSubyxUZHjhm6LsAaZX8q6GYVxkbT3kTsV9G5mBCFR6A==", + "dev": true, + "dependencies": { + "contains-path": "^0.1.0", + "debug": "^2.6.8", + "doctrine": "1.5.0", + "eslint-import-resolver-node": "^0.3.1", + "eslint-module-utils": "^2.2.0", + "has": "^1.0.1", + "lodash": "^4.17.4", + "minimatch": "^3.0.3", + "read-pkg-up": "^2.0.0", + "resolve": "^1.6.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "2.x - 5.x" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/doctrine": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", + "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", + "dev": true, + "dependencies": { + "esutils": "^2.0.2", + "isarray": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-restricted-globals": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/eslint-restricted-globals/-/eslint-restricted-globals-0.1.1.tgz", + "integrity": "sha1-NfDVy8ZMLj7WLpO0saevBbp+1Nc=", + "dev": true + }, + "node_modules/eslint-scope": { + "version": "3.7.3", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.3.tgz", + "integrity": "sha512-W+B0SvF4gamyCTmUc+uITPY0989iXVfKvhwtmJocTaYoc/3khEHmEmvfY/Gn9HA9VV75jrQECsHizkNw1b68FA==", + "dev": true, + "dependencies": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", + "integrity": "sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/espree": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.4.tgz", + "integrity": "sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A==", + "dev": true, + "dependencies": { + "acorn": "^5.5.0", + "acorn-jsx": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz", + "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", + "dev": true, + "dependencies": { + "estraverse": "^4.0.0" + }, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/esrecurse": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", + "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", + "dev": true, + "dependencies": { + "estraverse": "^4.1.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", + "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/esutils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/events": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", + "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=", + "engines": { + "node": ">=0.4.x" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "node_modules/external-editor": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz", + "integrity": "sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==", + "dev": true, + "dependencies": { + "chardet": "^0.4.0", + "iconv-lite": "^0.4.17", + "tmp": "^0.0.33" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "engines": [ + "node >=0.6.0" + ] + }, + "node_modules/fast-deep-equal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", + "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "node_modules/figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "dev": true, + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/file-entry-cache": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", + "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", + "dev": true, + "dependencies": { + "flat-cache": "^1.2.1", + "object-assign": "^4.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "dev": true, + "dependencies": { + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/flat-cache": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.0.tgz", + "integrity": "sha1-0wMLMrOBVPTjt+nHCfSQ9++XxIE=", + "dev": true, + "dependencies": { + "circular-json": "^0.3.1", + "del": "^2.0.2", + "graceful-fs": "^4.1.2", + "write": "^0.2.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dependencies": { + "is-callable": "^1.1.3" + } + }, + "node_modules/forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "engines": { + "node": "*" + } + }, + "node_modules/form-data": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz", + "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "1.0.6", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/fs-extra": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-6.0.1.tgz", + "integrity": "sha512-GnyIkKhhzXZUWFCaJzvyDLEEgDkPfb4/TPvJCJVuS8MWZgoSsErf++QpiAlDnKFcqhRlm+tIOcencCjyJE6ZCA==", + "dependencies": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "node_modules/functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, + "node_modules/fuse.js": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-6.4.1.tgz", + "integrity": "sha512-+hAS7KYgLXontDh/vqffs7wIBw0ceb9Sx8ywZQhOsiQGcSO5zInGhttWOUYQYlvV/yYMJOacQ129Xs3mP3+oZQ==", + "engines": { + "node": ">=10" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", + "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dependencies": { + "assert-plus": "^1.0.0" + } + }, + "node_modules/glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/globals": { + "version": "11.7.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.7.0.tgz", + "integrity": "sha512-K8BNSPySfeShBQXsahYB/AbbWruVOTyVpgoIDnl8odPpeSfP2J5QO2oLFFdl2j7GfDCtZj2bMKar2T49itTPCg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/globby": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", + "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", + "dev": true, + "dependencies": { + "array-union": "^1.0.1", + "arrify": "^1.0.0", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "engines": { + "node": ">=4" + } + }, + "node_modules/har-validator": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", + "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", + "deprecated": "this library is no longer supported", + "dependencies": { + "ajv": "^5.1.0", + "har-schema": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true, + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hosted-git-info": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz", + "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==", + "dev": true + }, + "node_modules/html-entities": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.2.1.tgz", + "integrity": "sha1-DfKTUfByEWNRXfueVUPl9u7VFi8=", + "engines": [ + "node >= 0.4.0" + ] + }, + "node_modules/http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "dependencies": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + }, + "engines": { + "node": ">=0.8", + "npm": ">=1.3.7" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", + "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" + }, + "node_modules/ignore": { + "version": "3.3.10", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", + "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", + "dev": true + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "node_modules/inquirer": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-3.3.0.tgz", + "integrity": "sha512-h+xtnyk4EwKvFWHrUYsWErEVR+igKtLdchu+o0Z1RL7VU/jVMFbYir2bp6bAj8efFNxWqHX0dIss6fJQ+/+qeQ==", + "dev": true, + "dependencies": { + "ansi-escapes": "^3.0.0", + "chalk": "^2.0.0", + "cli-cursor": "^2.1.0", + "cli-width": "^2.0.0", + "external-editor": "^2.0.4", + "figures": "^2.0.0", + "lodash": "^4.3.0", + "mute-stream": "0.0.7", + "run-async": "^2.2.0", + "rx-lite": "^4.0.8", + "rx-lite-aggregates": "^4.0.8", + "string-width": "^2.1.0", + "strip-ansi": "^4.0.0", + "through": "^2.3.6" + } + }, + "node_modules/is-arguments": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", + "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "node_modules/is-bluebird": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-bluebird/-/is-bluebird-1.0.2.tgz", + "integrity": "sha1-CWQ5Bg9KpBGr7hkUOoTWpVNG1uI=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-builtin-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", + "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", + "dev": true, + "dependencies": { + "builtin-modules": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/is-generator-function": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", + "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-path-cwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", + "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-path-in-cwd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz", + "integrity": "sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==", + "dev": true, + "dependencies": { + "is-path-inside": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-path-inside": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", + "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", + "dev": true, + "dependencies": { + "path-is-inside": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", + "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", + "dev": true + }, + "node_modules/is-resolvable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", + "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", + "dev": true + }, + "node_modules/is-typed-array": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", + "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "node_modules/isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + }, + "node_modules/jmespath": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.16.0.tgz", + "integrity": "sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw==", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/js-tokens": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", + "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", + "dev": true + }, + "node_modules/js-yaml": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.0.tgz", + "integrity": "sha512-PIt2cnwmPfL4hKNwqeiuz4bKfnzHTBv6HyVgjahA6mPLwPDzjDWrplJBMjHUFxku/N3FlmrbyPclad+I+4mJ3A==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "optional": true + }, + "node_modules/json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + }, + "node_modules/json-schema-traverse": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", + "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + }, + "node_modules/json5": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", + "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "engines": [ + "node >=0.6.0" + ], + "dependencies": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "node_modules/levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, + "dependencies": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/load-json-file": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", + "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "dependencies": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/locate-path/node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/lodash": { + "version": "4.17.10", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", + "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==", + "dev": true + }, + "node_modules/lru-cache": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.3.tgz", + "integrity": "sha512-fFEhvcgzuIoJVUF8fYr5KR0YqxD238zgObTps31YdADwPPAp82a4M8TrckkWyx7ekNlf9aBcVn81cFwwXngrJA==", + "dev": true, + "dependencies": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.35.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.35.0.tgz", + "integrity": "sha512-JWT/IcCTsB0Io3AhWUMjRqucrHSPsSf2xKLaRldJVULioggvkJvggZ3VXNNSRkCddE6D+BUI4HEIZIA2OjwIvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.19", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.19.tgz", + "integrity": "sha512-P1tKYHVSZ6uFo26mtnve4HQFE3koh1UWVkp8YUC+ESBHe945xWSoXuHHiGarDqcEZ+whpCDnlNw5LON0kLo+sw==", + "dependencies": { + "mime-db": "~1.35.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", + "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=" + }, + "node_modules/mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)", + "dev": true, + "dependencies": { + "minimist": "0.0.8" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/mkdirp/node_modules/minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "node_modules/music-metadata": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/music-metadata/-/music-metadata-1.1.0.tgz", + "integrity": "sha512-HTIWhewHyLwVQByhh45BGtmxrEDpq4RI4ZwUKYBwj7uPheE+MmudlnRc22emq99GRLGmb16/Q7RgM+3O0Wi2wg==", + "dependencies": { + "bluebird": "^3.5.1", + "debug": "^3.1.0", + "fs-extra": "^6.0.1", + "strtok3": "^1.4.2", + "then-read-stream": "^1.1.3", + "token-types": "^0.9.4" + }, + "engines": { + "node": "*" + } + }, + "node_modules/music-metadata/node_modules/bluebird": { + "version": "3.5.5", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.5.tgz", + "integrity": "sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w==" + }, + "node_modules/mute-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", + "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", + "dev": true + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "node_modules/node-static": { + "version": "0.7.10", + "resolved": "https://registry.npmjs.org/node-static/-/node-static-0.7.10.tgz", + "integrity": "sha512-bd7zO5hvCWzdglgwz9t82T4mYTEUzEG5pXnSqEzitvmEacusbhl8/VwuCbMaYR9g2PNK5191yBtAEQLJEmQh1A==", + "dependencies": { + "colors": ">=0.6.0", + "mime": "^1.2.9", + "optimist": ">=0.3.4" + }, + "bin": { + "static": "bin/cli.js" + }, + "engines": { + "node": ">= 0.4.1" + } + }, + "node_modules/normalize-package-data": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", + "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==", + "dev": true, + "dependencies": { + "hosted-git-info": "^2.1.4", + "is-builtin-module": "^1.0.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "node_modules/oauth-sign": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", + "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=", + "engines": { + "node": "*" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "dev": true, + "dependencies": { + "mimic-fn": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/optimist": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", + "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", + "dependencies": { + "minimist": "~0.0.1", + "wordwrap": "~0.0.2" + } + }, + "node_modules/optionator": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", + "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", + "dev": true, + "dependencies": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.4", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "wordwrap": "~1.0.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/optionator/node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", + "dev": true + }, + "node_modules/os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "dependencies": { + "p-try": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "dependencies": { + "p-limit": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "dev": true, + "dependencies": { + "error-ex": "^1.2.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "dev": true, + "dependencies": { + "pinkie-promise": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", + "dev": true + }, + "node_modules/path-parse": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz", + "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=", + "dev": true + }, + "node_modules/path-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", + "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", + "dev": true, + "dependencies": { + "pify": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dev": true, + "dependencies": { + "pinkie": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pkg-dir": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-1.0.0.tgz", + "integrity": "sha1-ektQio1bstYp1EcFb/TpyTFM89Q=", + "dev": true, + "dependencies": { + "find-up": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pluralize": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-7.0.0.tgz", + "integrity": "sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", + "dev": true + }, + "node_modules/progress": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.0.tgz", + "integrity": "sha1-ihvjZr+Pwj2yvSPxDG/pILQ4nR8=", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", + "dev": true + }, + "node_modules/punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + }, + "node_modules/qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", + "deprecated": "The querystring API is considered Legacy. new code should use the URLSearchParams API instead.", + "engines": { + "node": ">=0.4.x" + } + }, + "node_modules/read-pkg": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", + "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", + "dev": true, + "dependencies": { + "load-json-file": "^2.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/read-pkg-up": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", + "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", + "dev": true, + "dependencies": { + "find-up": "^2.0.0", + "read-pkg": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/read-pkg-up/node_modules/find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "dependencies": { + "locate-path": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/regexpp": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-1.1.0.tgz", + "integrity": "sha512-LOPw8FpgdQF9etWMaAfG/WRthIdXJGYp4mJ2Jgn/2lpkbod9jPn0t9UqN7AxBOKNfzRbYyVfgc7Vk4t/MpnXgw==", + "dev": true, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/request": { + "version": "2.87.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.87.0.tgz", + "integrity": "sha512-fcogkm7Az5bsS6Sl0sibkbhcKsnyon/jV1kF3ajGmF0c8HrttdKTPRT9hieOaQHA5HEq6r8OyWOo/o781C1tNw==", + "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142", + "dependencies": { + "aws-sign2": "~0.7.0", + "aws4": "^1.6.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.5", + "extend": "~3.0.1", + "forever-agent": "~0.6.1", + "form-data": "~2.3.1", + "har-validator": "~5.0.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.17", + "oauth-sign": "~0.8.2", + "performance-now": "^2.1.0", + "qs": "~6.5.1", + "safe-buffer": "^5.1.1", + "tough-cookie": "~2.3.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.1.0" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/request-promise": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/request-promise/-/request-promise-1.0.2.tgz", + "integrity": "sha1-FV9BBgjZJXwInB0LJvjY96iqhqE=", + "deprecated": "request-promise has been deprecated because it extends the now deprecated request package, see https://github.com/request/request/issues/3142", + "dependencies": { + "bluebird": "^2.3", + "cls-bluebird": "^1.0.1", + "lodash": "^3.10.0", + "request": "^2.34" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/request-promise/node_modules/lodash": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", + "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=" + }, + "node_modules/require-uncached": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", + "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", + "dev": true, + "dependencies": { + "caller-path": "^0.1.0", + "resolve-from": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.8.1.tgz", + "integrity": "sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA==", + "dev": true, + "dependencies": { + "path-parse": "^1.0.5" + } + }, + "node_modules/resolve-from": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", + "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/restore-cursor": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", + "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", + "dev": true, + "dependencies": { + "onetime": "^2.0.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/rimraf": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", + "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", + "dev": true, + "dependencies": { + "glob": "^7.0.5" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/run-async": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", + "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", + "dev": true, + "dependencies": { + "is-promise": "^2.1.0" + }, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/rx-lite": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-4.0.8.tgz", + "integrity": "sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ=", + "dev": true + }, + "node_modules/rx-lite-aggregates": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz", + "integrity": "sha1-dTuHqJoRyVRnxKwWJsTvxOBcZ74=", + "dev": true, + "dependencies": { + "rx-lite": "*" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/sax": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", + "integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o=" + }, + "node_modules/semver": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", + "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "dependencies": { + "shebang-regex": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/shimmer": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.2.0.tgz", + "integrity": "sha512-xTCx2vohXC2EWWDqY/zb4+5Mu28D+HYNSOuFzsyRDRvI/e1ICb69afwaUwfjr+25ZXldbOLyp+iDUZHq8UnTag==" + }, + "node_modules/signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "dev": true + }, + "node_modules/slice-ansi": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-1.0.0.tgz", + "integrity": "sha512-POqxBK6Lb3q6s047D/XsDVNPnF9Dl8JSaqe9h9lURl0OdNqy/ujDrOiIHtsqXMGbWWTIomRzAMaTyawAU//Reg==", + "dev": true, + "dependencies": { + "is-fullwidth-code-point": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/sonos-discovery": { + "version": "1.7.3", + "resolved": "https://github.com/jishi/node-sonos-discovery/archive/v1.7.3.tar.gz", + "integrity": "sha512-bHog/7+zjirplyZOjclSM9g6rZVZX0bDWX+PzkS6KN0I1/KK6SjotazhkI4nhcrx4NYWnxCQULIO9RvzlyG2uA==", + "dependencies": { + "html-entities": "1.0.x", + "xml-flow": "1.0.2" + } + }, + "node_modules/sonos-discovery/node_modules/html-entities": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.0.10.tgz", + "integrity": "sha1-DepZEw3VDfFU6CxM9x0Iwsnclos=", + "engines": [ + "node >= 0.4.0" + ] + }, + "node_modules/spdx-correct": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.0.0.tgz", + "integrity": "sha512-N19o9z5cEyc8yQQPukRCZ9EUmb4HUpnrmaL/fxS2pBo2jbfcFRVuFZ/oFC+vZz0MNNk0h80iMn5/S6qGZOL5+g==", + "dev": true, + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.1.0.tgz", + "integrity": "sha512-4K1NsmrlCU1JJgUrtgEeTVyfx8VaYea9J9LvARxhbHtVtohPs/gFGG5yy49beySjlIMhhXZ4QqujIZEfS4l6Cg==", + "dev": true + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", + "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", + "dev": true, + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.0.tgz", + "integrity": "sha512-2+EPwgbnmOIl8HjGBXXMd9NAu02vLjOO1nWw4kmeRDFyHn+M/ETfHxQUK0oXg8ctgVnl9t3rosNVsZ1jG61nDA==", + "dev": true + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "node_modules/sshpk": { + "version": "1.14.2", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.2.tgz", + "integrity": "sha1-xvxhZIo9nE52T9P8306hBeSSupg=", + "dependencies": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "dashdash": "^1.12.0", + "getpass": "^0.1.1", + "safer-buffer": "^2.0.2" + }, + "bin": { + "sshpk-conv": "bin/sshpk-conv", + "sshpk-sign": "bin/sshpk-sign", + "sshpk-verify": "bin/sshpk-verify" + }, + "engines": { + "node": ">=0.10.0" + }, + "optionalDependencies": { + "bcrypt-pbkdf": "^1.0.0", + "ecc-jsbn": "~0.1.1", + "jsbn": "~0.1.0", + "tweetnacl": "~0.14.0" + } + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "dependencies": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "dependencies": { + "ansi-regex": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-ansi/node_modules/ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strtok3": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-1.6.0.tgz", + "integrity": "sha512-BDr7b5RJSfvX1wBjJDwpJOGZ0EhfhZaMCm+YaOVAcvFgo8/+nJq/Vurzh7JykAgiW+EOo5kkrfVffBFFp3u8TQ==", + "dependencies": { + "debug": "^3.1.0", + "es6-promise": "^4.2.4", + "then-read-stream": "^1.2.1", + "token-types": "^0.10.0" + }, + "engines": { + "node": ">=0.1.98" + } + }, + "node_modules/strtok3/node_modules/token-types": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/token-types/-/token-types-0.10.0.tgz", + "integrity": "sha512-26A0816VoHW8h64OT47dcclsH5M9iwo9zi3KoCPz1NrKoI9T2dlVkisgzTaGK4dPjCbP3ugf7cYqhWOiAzVHgw==", + "engines": { + "node": ">=0.1.98" + } + }, + "node_modules/supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/table": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/table/-/table-4.0.2.tgz", + "integrity": "sha512-UUkEAPdSGxtRpiV9ozJ5cMTtYiqz7Ni1OGqLXRCynrvzdtR1p+cfOWe2RJLwvUG8hNanaSRjecIqwOjqeatDsA==", + "dev": true, + "dependencies": { + "ajv": "^5.2.3", + "ajv-keywords": "^2.1.0", + "chalk": "^2.1.0", + "lodash": "^4.17.4", + "slice-ansi": "1.0.0", + "string-width": "^2.1.1" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "node_modules/then-read-stream": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/then-read-stream/-/then-read-stream-1.5.1.tgz", + "integrity": "sha512-I+iiemYWhp1ysJQEioqpEICgvHlqHS5WrQGZkboFLs7Jm350Kvq4cN3qRCzHpETUuq5+NsdrdWEg6M0NFxtwtQ==", + "deprecated": "Package renamed to peak-readable.", + "engines": { + "node": ">=8" + } + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "node_modules/tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "dependencies": { + "os-tmpdir": "~1.0.2" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/token-types": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/token-types/-/token-types-0.9.4.tgz", + "integrity": "sha512-KSl/Q1GJ4FoxbqKCLhTiIowVzom2cP0fgWGXKsJupbJQqfnCDmxkdMopIrt+y5Ak6YAGdy9TKpplWDioaBsbEw==", + "engines": { + "node": ">=0.1.98" + } + }, + "node_modules/tough-cookie": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz", + "integrity": "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==", + "dependencies": { + "punycode": "^1.4.1" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "optional": true + }, + "node_modules/type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, + "dependencies": { + "prelude-ls": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true + }, + "node_modules/underscore": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.1.tgz", + "integrity": "sha512-5/4etnCkd9c8gwgowi5/om/mYO5ajCaOgdzj/oW+0eQV9WxKBDZw5+ycmKmeaTXjInS/W0BzpGLo2xR2aBwZdg==" + }, + "node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/url": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", + "integrity": "sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=", + "dependencies": { + "punycode": "1.3.2", + "querystring": "0.2.0" + } + }, + "node_modules/url/node_modules/punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" + }, + "node_modules/util": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", + "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", + "dependencies": { + "inherits": "^2.0.3", + "is-arguments": "^1.0.4", + "is-generator-function": "^1.0.7", + "is-typed-array": "^1.1.3", + "which-typed-array": "^1.1.2" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "node_modules/uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/validate-npm-package-license": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.3.tgz", + "integrity": "sha512-63ZOUnL4SIXj4L0NixR3L1lcjO38crAbgrTpl28t8jjrfuiOBL5Iygm+60qPs/KsZGzPNg6Smnc/oY16QTjF0g==", + "dev": true, + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "engines": [ + "node >=0.6.0" + ], + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "node_modules/wav-file-info": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/wav-file-info/-/wav-file-info-0.0.8.tgz", + "integrity": "sha1-aAp160w0a34/RX55AqexmDUG5N4=" + }, + "node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", + "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/wordwrap": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", + "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "node_modules/write": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", + "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", + "dev": true, + "dependencies": { + "mkdirp": "^0.5.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/xml-flow": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/xml-flow/-/xml-flow-1.0.2.tgz", + "integrity": "sha512-TfV52Su2083n8w61l1hGI5K3ZAkn4hmaon3oJHc+TRQo0QkKLMPAPdMpGMJd+5Aik6ZUicPDjBINO8QxLkZGgg==", + "dependencies": { + "sax": "^1.2.4" + } + }, + "node_modules/xml-flow/node_modules/sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, + "node_modules/xml2js": { + "version": "0.4.19", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", + "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~9.0.1" + } + }, + "node_modules/xmlbuilder": { + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", + "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", + "dev": true + } + }, "dependencies": { "acorn": { "version": "5.7.1", @@ -116,26 +2884,32 @@ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, + "available-typed-arrays": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==" + }, "aws-sdk": { - "version": "2.284.1", - "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.284.1.tgz", - "integrity": "sha512-/fV2RGGhL13LH7xjccmAh/uoNEa3z+MWHRmHq+Arj2hKgpPoRNCC+8lLIAhxKcPLdJt/G9rcm2EnDqT9E/1aSA==", + "version": "2.1299.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1299.0.tgz", + "integrity": "sha512-xTh6pmCUEJljkFfTM3sE8UozDxal80uX/5WZl8GcjQ+NbrGeQEdvL6wFWBwEEVbhR0VBVuU37cKPuQlfENbRYA==", "requires": { - "buffer": "4.9.1", + "buffer": "4.9.2", "events": "1.1.1", - "ieee754": "1.1.8", - "jmespath": "0.15.0", + "ieee754": "1.1.13", + "jmespath": "0.16.0", "querystring": "0.2.0", "sax": "1.2.1", "url": "0.10.3", - "uuid": "3.1.0", + "util": "^0.12.4", + "uuid": "8.0.0", "xml2js": "0.4.19" }, "dependencies": { "uuid": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz", - "integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g==" + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.0.0.tgz", + "integrity": "sha512-jOXGuXZAWdsTH7eZLtyXMqUb9EcWMGZNbL9YcGBJl4MH4nrxHmZJhEHvyLFrkxo+28uLb/NYRcStH48fnD0Vzw==" } } }, @@ -191,9 +2965,9 @@ "dev": true }, "base64-js": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.0.tgz", - "integrity": "sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw==" + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" }, "basic-auth": { "version": "1.1.0", @@ -225,9 +2999,9 @@ } }, "buffer": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", - "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", + "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", "requires": { "base64-js": "^1.0.2", "ieee754": "^1.1.4", @@ -246,6 +3020,15 @@ "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", "dev": true }, + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, "caller-path": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", @@ -774,6 +3557,14 @@ "write": "^0.2.1" } }, + "for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "requires": { + "is-callable": "^1.1.3" + } + }, "forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", @@ -808,8 +3599,7 @@ "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, "functional-red-black-tree": { "version": "1.0.1", @@ -822,6 +3612,16 @@ "resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-6.4.1.tgz", "integrity": "sha512-+hAS7KYgLXontDh/vqffs7wIBw0ceb9Sx8ywZQhOsiQGcSO5zInGhttWOUYQYlvV/yYMJOacQ129Xs3mP3+oZQ==" }, + "get-intrinsic": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", + "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.3" + } + }, "getpass": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", @@ -864,6 +3664,14 @@ "pinkie-promise": "^2.0.0" } }, + "gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "requires": { + "get-intrinsic": "^1.1.3" + } + }, "graceful-fs": { "version": "4.1.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", @@ -887,7 +3695,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, "requires": { "function-bind": "^1.1.1" } @@ -907,6 +3714,19 @@ "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", "dev": true }, + "has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" + }, + "has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "requires": { + "has-symbols": "^1.0.2" + } + }, "hosted-git-info": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz", @@ -938,9 +3758,9 @@ } }, "ieee754": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.8.tgz", - "integrity": "sha1-vjPUCsEO8ZJnAfbwii2G+/0a0+Q=" + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" }, "ignore": { "version": "3.3.10", @@ -967,8 +3787,7 @@ "inherits": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" }, "inquirer": { "version": "3.3.0", @@ -992,6 +3811,15 @@ "through": "^2.3.6" } }, + "is-arguments": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", + "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, "is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", @@ -1012,12 +3840,25 @@ "builtin-modules": "^1.0.0" } }, + "is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==" + }, "is-fullwidth-code-point": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", "dev": true }, + "is-generator-function": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", + "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "requires": { + "has-tostringtag": "^1.0.0" + } + }, "is-path-cwd": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", @@ -1054,6 +3895,18 @@ "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", "dev": true }, + "is-typed-array": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", + "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", + "requires": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0" + } + }, "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", @@ -1076,9 +3929,9 @@ "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" }, "jmespath": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz", - "integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc=" + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.16.0.tgz", + "integrity": "sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw==" }, "js-tokens": { "version": "3.0.2", @@ -1823,6 +4676,15 @@ "tweetnacl": "~0.14.0" } }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, "string-width": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", @@ -1833,15 +4695,6 @@ "strip-ansi": "^4.0.0" } }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - }, "strip-ansi": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", @@ -2003,6 +4856,18 @@ } } }, + "util": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", + "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", + "requires": { + "inherits": "^2.0.3", + "is-arguments": "^1.0.4", + "is-generator-function": "^1.0.7", + "is-typed-array": "^1.1.3", + "which-typed-array": "^1.1.2" + } + }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -2048,6 +4913,19 @@ "isexe": "^2.0.0" } }, + "which-typed-array": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", + "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", + "requires": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0", + "is-typed-array": "^1.1.10" + } + }, "wordwrap": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", diff --git a/package.json b/package.json index c19a4db9..db24d762 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ }, "dependencies": { "anesidora": "^1.2.0", - "aws-sdk": "^2.12.0", + "aws-sdk": "^2.1299.0", "basic-auth": "~1.1.0", "fuse.js": "^6.4.1", "html-entities": "^1.2.1", From 7e66c58b4e3c5b0e7f1d89808a721fdfcfb0853a Mon Sep 17 00:00:00 2001 From: mgorsk1 Date: Wed, 20 Dec 2023 09:48:22 +0100 Subject: [PATCH 113/123] :tada: Init --- README.md | 36 ++++++++++++ lib/tts-providers/elevenlabs.js | 99 +++++++++++++++++++++++++++++++++ package.json | 3 +- 3 files changed, 137 insertions(+), 1 deletion(-) create mode 100644 lib/tts-providers/elevenlabs.js diff --git a/README.md b/README.md index b3bacf0e..14c1556d 100644 --- a/README.md +++ b/README.md @@ -403,6 +403,7 @@ Experimental support for TTS. Today the following providers are available: * AWS Polly * Google (default) * macOS say command +* Elevenlabs It will use the one you configure in settings.json. If you define settings for multiple TTS services, it will not be guaranteed which one it will choose! @@ -610,6 +611,41 @@ To get a current list of voices, you would need to use the AWS CLI and invoke th | Welsh | cy-GB | Female | Gwyneth | | Welsh English | en-GB-WLS | Male | Geraint | +#### Elevenlabs + +Elevenlabs is a TTS service enabling generatiung TTS audio files using AI generated voices. + +Requires API Key and optionally default voiceId. + +Since Elevenlabs AI models are multilingual by default, there is no need (nor place) for `language` parameter in +Elevenlabs API. Because of this, `language` parameter in URL is used to inject custom `voiceId` on per-request basis. You will +need to either configure default voiceId in `settings.json` or provide `voiceId` with every HTTP request. + +##### Config + +Minimal: +```json + { + "elevenlabs": { + "apiKey": "" + } + } +``` + +Full: +```json + { + "elevenlabs": { + "apiKey": "", + "voiceId": "", + "stability": 0.5, + "similarityBoost": 0.5, + "speakerBoost": true, + "style": 1 + } + } +``` + #### Google (default if no other has been configured) Does not require any API keys. Please note that Google has been known in the past to change the requirements for its Text-to-Speech API, and this may stop working in the future. There is also limiations to how many requests one is allowed to do in a specific time period. diff --git a/lib/tts-providers/elevenlabs.js b/lib/tts-providers/elevenlabs.js new file mode 100644 index 00000000..e7e7aeb6 --- /dev/null +++ b/lib/tts-providers/elevenlabs.js @@ -0,0 +1,99 @@ +'use strict'; +const crypto = require('crypto'); +const fs = require('fs'); +const http = require('http'); +const path = require('path'); +const ElevenLabs = require("elevenlabs-node"); +const fileDuration = require('../helpers/file-duration'); +const settings = require('../../settings'); +const logger = require('sonos-discovery/lib/helpers/logger'); + +const DEFAULT_SETTINGS = { + stability: 0.5, + similarityBoost: 0.5, + speakerBoost: true, + style: 1, +}; + +// Provider developed based on structure from aws-polly.js. +// In this tts provider language argument from uri is used to inject custom voiceId +function eleven(phrase, voiceId) { + if (!settings.elevenlabs) { + return Promise.resolve(); + } + + // Construct a filesystem neutral filename + const dynamicParameters = { textInput: phrase }; + const synthesizeParameters = Object.assign({}, DEFAULT_SETTINGS, dynamicParameters); + + if (settings.elevenlabs.stability) { + synthesizeParameters.stability = settings.elevenlabs.stability; + } + if (settings.elevenlabs.similarityBoost) { + synthesizeParameters.similarityBoost = settings.elevenlabs.similarityBoost; + } + if (settings.elevenlabs.modelId) { + synthesizeParameters.modelId = settings.elevenlabs.modelId; + } + if (settings.elevenlabs.style) { + synthesizeParameters.style = settings.elevenlabs.style; + } + if (settings.elevenlabs.speakerBoost) { + synthesizeParameters.speakerBoost = settings.elevenlabs.speakerBoost; + } + + let targetVoiceId; + if (voiceId) { + targetVoiceId = voiceId; + } + else if (settings.elevenlabs.voiceId) { + targetVoiceId = settings.elevenlabs.voiceId; + } + else { + console.log('Voice ID not provided neither as language nor in settings.') + return Promise.resolve(); + } + + const phraseHash = crypto.createHash('sha1').update(phrase).digest('hex'); + const filename = `elevenlabs-${phraseHash}-${targetVoiceId}.mp3`; + const filepath = path.resolve(settings.webroot, 'tts', filename); + + synthesizeParameters.fileName = filepath; + + const expectedUri = `/tts/${filename}`; + try { + fs.accessSync(filepath, fs.R_OK); + return fileDuration(filepath) + .then((duration) => { + return { + duration, + uri: expectedUri + }; + }); + } catch (err) { + logger.info(`announce file for phrase "${phrase}" does not seem to exist, downloading`); + } + + const voice = new ElevenLabs( + { + apiKey: settings.elevenlabs.apiKey, // Your API key from Elevenlabs + voiceId: targetVoiceId, // A Voice ID from Elevenlabs + } + ); + + return voice.textToSpeech(synthesizeParameters) + .then((res) => { + console.log('Elevenlabs TTS generated new audio file.'); + }) + .then(() => { + return fileDuration(filepath); + }) + .then((duration) => { + return { + duration, + uri: expectedUri + }; + }); +} + +module.exports = eleven; diff --git a/package.json b/package.json index db24d762..f930b987 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,8 @@ "node-static": "^0.7.9", "request-promise": "~1.0.2", "sonos-discovery": "https://github.com/jishi/node-sonos-discovery/archive/v1.7.3.tar.gz", - "wav-file-info": "0.0.8" + "wav-file-info": "0.0.8", + "elevenlabs-node": "2.0.1" }, "engines": { "node": ">=4.0.0", From b0c81494e53230237c7448306d0c4741e5408f24 Mon Sep 17 00:00:00 2001 From: mgorsk1 Date: Wed, 20 Dec 2023 09:49:55 +0100 Subject: [PATCH 114/123] :fire: Removing code or files. --- lib/tts-providers/elevenlabs.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/tts-providers/elevenlabs.js b/lib/tts-providers/elevenlabs.js index e7e7aeb6..796f7814 100644 --- a/lib/tts-providers/elevenlabs.js +++ b/lib/tts-providers/elevenlabs.js @@ -32,9 +32,6 @@ function eleven(phrase, voiceId) { if (settings.elevenlabs.similarityBoost) { synthesizeParameters.similarityBoost = settings.elevenlabs.similarityBoost; } - if (settings.elevenlabs.modelId) { - synthesizeParameters.modelId = settings.elevenlabs.modelId; - } if (settings.elevenlabs.style) { synthesizeParameters.style = settings.elevenlabs.style; } From 50b5a171b1639acaa5003a328be86296aaf92dc4 Mon Sep 17 00:00:00 2001 From: mgorsk1 Date: Wed, 20 Dec 2023 22:40:12 +0100 Subject: [PATCH 115/123] :ok_hand: Updating code due to code review changes. --- README.md | 20 +++++++++++++------- lib/tts-providers/elevenlabs.js | 33 ++++++++------------------------- 2 files changed, 21 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index 14c1556d..bc205af3 100644 --- a/README.md +++ b/README.md @@ -627,7 +627,9 @@ Minimal: ```json { "elevenlabs": { - "apiKey": "" + "auth": { + "apiKey": "" + } } } ``` @@ -636,12 +638,16 @@ Full: ```json { "elevenlabs": { - "apiKey": "", - "voiceId": "", - "stability": 0.5, - "similarityBoost": 0.5, - "speakerBoost": true, - "style": 1 + "auth": { + "apiKey": "" + }, + "config": { + "voiceId": "", + "stability": 0.5, + "similarityBoost": 0.5, + "speakerBoost": true, + "style": 1 + } } } ``` diff --git a/lib/tts-providers/elevenlabs.js b/lib/tts-providers/elevenlabs.js index 796f7814..937b1742 100644 --- a/lib/tts-providers/elevenlabs.js +++ b/lib/tts-providers/elevenlabs.js @@ -3,7 +3,7 @@ const crypto = require('crypto'); const fs = require('fs'); const http = require('http'); const path = require('path'); -const ElevenLabs = require("elevenlabs-node"); +const ElevenLabs = require('elevenlabs-node'); const fileDuration = require('../helpers/file-duration'); const settings = require('../../settings'); const logger = require('sonos-discovery/lib/helpers/logger'); @@ -24,35 +24,19 @@ function eleven(phrase, voiceId) { // Construct a filesystem neutral filename const dynamicParameters = { textInput: phrase }; - const synthesizeParameters = Object.assign({}, DEFAULT_SETTINGS, dynamicParameters); + const synthesizeParameters = Object.assign({}, DEFAULT_SETTINGS, dynamicParameters, settings.elevenlabs.config); - if (settings.elevenlabs.stability) { - synthesizeParameters.stability = settings.elevenlabs.stability; - } - if (settings.elevenlabs.similarityBoost) { - synthesizeParameters.similarityBoost = settings.elevenlabs.similarityBoost; - } - if (settings.elevenlabs.style) { - synthesizeParameters.style = settings.elevenlabs.style; - } - if (settings.elevenlabs.speakerBoost) { - synthesizeParameters.speakerBoost = settings.elevenlabs.speakerBoost; - } - - let targetVoiceId; if (voiceId) { - targetVoiceId = voiceId; - } - else if (settings.elevenlabs.voiceId) { - targetVoiceId = settings.elevenlabs.voiceId; + synthesizeParameters.voiceId = voiceId; } - else { - console.log('Voice ID not provided neither as language nor in settings.') + + if (!synthesizeParameters.voiceId) { + console.log('Voice ID not found neither in settings.elevenlabs.config nor in request!') return Promise.resolve(); } const phraseHash = crypto.createHash('sha1').update(phrase).digest('hex'); - const filename = `elevenlabs-${phraseHash}-${targetVoiceId}.mp3`; + const filename = `elevenlabs-${phraseHash}-${synthesizeParameters.voiceId}.mp3`; const filepath = path.resolve(settings.webroot, 'tts', filename); synthesizeParameters.fileName = filepath; @@ -73,8 +57,7 @@ function eleven(phrase, voiceId) { const voice = new ElevenLabs( { - apiKey: settings.elevenlabs.apiKey, // Your API key from Elevenlabs - voiceId: targetVoiceId, // A Voice ID from Elevenlabs + apiKey: settings.elevenlabs.auth.apiKey } ); From ae802fbe882efc574d4eba7ada0bda38ca4ff475 Mon Sep 17 00:00:00 2001 From: mgorsk1 Date: Fri, 22 Dec 2023 08:22:03 +0100 Subject: [PATCH 116/123] add default model multilingual --- lib/tts-providers/elevenlabs.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/tts-providers/elevenlabs.js b/lib/tts-providers/elevenlabs.js index 937b1742..a0341f6c 100644 --- a/lib/tts-providers/elevenlabs.js +++ b/lib/tts-providers/elevenlabs.js @@ -13,6 +13,7 @@ const DEFAULT_SETTINGS = { similarityBoost: 0.5, speakerBoost: true, style: 1, + modelId: "eleven_multilingual_v2" }; // Provider developed based on structure from aws-polly.js. From e8e0f21e44ea5bf39a8f244b18e9ca7ab44bd6f7 Mon Sep 17 00:00:00 2001 From: mgorsk1 Date: Fri, 22 Dec 2023 09:02:27 +0100 Subject: [PATCH 117/123] rebase upstream package-lock.json --- package-lock.json | 193 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 183 insertions(+), 10 deletions(-) diff --git a/package-lock.json b/package-lock.json index 94f21c64..2725c41a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "anesidora": "^1.2.0", "aws-sdk": "^2.1299.0", "basic-auth": "~1.1.0", + "elevenlabs-node": "2.0.1", "fuse.js": "^6.4.1", "html-entities": "^1.2.1", "json5": "^0.5.1", @@ -230,6 +231,40 @@ "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.7.0.tgz", "integrity": "sha512-32NDda82rhwD9/JBCCkB+MRYDp0oSvlo2IL6rQWA10PQi7tDUM3eqMSltXmY+Oyl/7N3P3qNtAlv7X0d9bI28w==" }, + "node_modules/axios": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz", + "integrity": "sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==", + "dependencies": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/axios/node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/axios/node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/babel-code-frame": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", @@ -629,6 +664,47 @@ "safer-buffer": "^2.1.0" } }, + "node_modules/elevenlabs-node": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/elevenlabs-node/-/elevenlabs-node-2.0.1.tgz", + "integrity": "sha512-q6vUznhudS1yxYxTVP3sT3xWzSzoXH+y3T+4GE1X8gjGwJw1Inmko/paQfl4d+CpTQXGNWuACAlFUdIk96xebw==", + "dependencies": { + "axios": "^1.4.0", + "fs-extra": "^11.1.1" + } + }, + "node_modules/elevenlabs-node/node_modules/fs-extra": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", + "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/elevenlabs-node/node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/elevenlabs-node/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -1007,6 +1083,25 @@ "node": ">=0.10.0" } }, + "node_modules/follow-redirects": { + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", + "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/for-each": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", @@ -1147,12 +1242,9 @@ } }, "node_modules/graceful-fs": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", - "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", - "engines": { - "node": ">=0.4.0" - } + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" }, "node_modules/har-schema": { "version": "2.0.0", @@ -2028,6 +2120,11 @@ "node": ">=0.4.0" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "node_modules/pseudomap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", @@ -2810,7 +2907,8 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-2.1.1.tgz", "integrity": "sha1-YXmX/F9gV2iUxDX5QNgZ4TW4B2I=", - "dev": true + "dev": true, + "requires": {} }, "anesidora": { "version": "1.2.1", @@ -2923,6 +3021,36 @@ "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.7.0.tgz", "integrity": "sha512-32NDda82rhwD9/JBCCkB+MRYDp0oSvlo2IL6rQWA10PQi7tDUM3eqMSltXmY+Oyl/7N3P3qNtAlv7X0d9bI28w==" }, + "axios": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz", + "integrity": "sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==", + "requires": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + }, + "dependencies": { + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + } + } + }, "babel-code-frame": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", @@ -3250,6 +3378,41 @@ "safer-buffer": "^2.1.0" } }, + "elevenlabs-node": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/elevenlabs-node/-/elevenlabs-node-2.0.1.tgz", + "integrity": "sha512-q6vUznhudS1yxYxTVP3sT3xWzSzoXH+y3T+4GE1X8gjGwJw1Inmko/paQfl4d+CpTQXGNWuACAlFUdIk96xebw==", + "requires": { + "axios": "^1.4.0", + "fs-extra": "^11.1.1" + }, + "dependencies": { + "fs-extra": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", + "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + }, + "jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "requires": { + "graceful-fs": "^4.1.6", + "universalify": "^2.0.0" + } + }, + "universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==" + } + } + }, "error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -3557,6 +3720,11 @@ "write": "^0.2.1" } }, + "follow-redirects": { + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", + "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==" + }, "for-each": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", @@ -3673,9 +3841,9 @@ } }, "graceful-fs": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", - "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=" + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" }, "har-schema": { "version": "2.0.0", @@ -4364,6 +4532,11 @@ "integrity": "sha1-ihvjZr+Pwj2yvSPxDG/pILQ4nR8=", "dev": true }, + "proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "pseudomap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", From 98f6ffeefef94e0342a51c6ab34124cc9329e33c Mon Sep 17 00:00:00 2001 From: mgorsk1 Date: Sat, 23 Dec 2023 11:11:26 +0100 Subject: [PATCH 118/123] :bulb: Documenting source code. --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index bc205af3..15806a76 100644 --- a/README.md +++ b/README.md @@ -646,7 +646,8 @@ Full: "stability": 0.5, "similarityBoost": 0.5, "speakerBoost": true, - "style": 1 + "style": 1, + "modelId": "eleven_multilingual_v2" } } } From 1fa4cdc998d6cf7b0448a2c622b51d352dd05a0b Mon Sep 17 00:00:00 2001 From: Barloew Date: Thu, 18 Jan 2024 12:09:53 +0100 Subject: [PATCH 119/123] Update README.md Add SonoBoss reference project, --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 15806a76..bb0b00f9 100644 --- a/README.md +++ b/README.md @@ -1231,3 +1231,8 @@ https://github.com/cjrpaterson/sonos-cron A Node server to receive notifications from node-sonos-http-api and push them via socket.io to the clients. https://github.com/TimoKorinth/sonos-push-server +**SonoBoss (Siri Shortcut)** + +A ChatGPT-assisted Siri Shortcut that acts as a virtual assistant to let you find music and control Sonos through voice and chat. +https://github.com/Barloew/SonoBoss + From fbcb628714fe6811d55492c03f1c135139713876 Mon Sep 17 00:00:00 2001 From: crissmil Date: Tue, 7 May 2024 00:42:07 +0300 Subject: [PATCH 120/123] server.js replace node-static with serve-static --- server.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/server.js b/server.js index b795e13c..f59d6abd 100644 --- a/server.js +++ b/server.js @@ -6,22 +6,22 @@ const auth = require('basic-auth'); const SonosSystem = require('sonos-discovery'); const logger = require('sonos-discovery/lib/helpers/logger'); const SonosHttpAPI = require('./lib/sonos-http-api.js'); -const nodeStatic = require('node-static'); +const serveStatic = require('serve-static'); const settings = require('./settings'); -const fileServer = new nodeStatic.Server(settings.webroot); +const serve = new serveStatic(settings.webroot); const discovery = new SonosSystem(settings); const api = new SonosHttpAPI(discovery, settings); var requestHandler = function (req, res) { req.addListener('end', function () { - fileServer.serve(req, res, function (err) { + serve(req, res, function (err) { // If error, route it. // This bypasses authentication on static files! - if (!err) { - return; - } + //if (!err) { + // return; + //} if (settings.auth) { var credentials = auth(req); From 14798ffa632fdfab948c2773bac3eb08189a89b6 Mon Sep 17 00:00:00 2001 From: crissmil Date: Tue, 7 May 2024 00:48:23 +0300 Subject: [PATCH 121/123] Update package.json with serve-static --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f930b987..5730bd53 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "json5": "^0.5.1", "mime": "^1.4.1", "music-metadata": "^1.1.0", - "node-static": "^0.7.9", + "serve-static": "^1.15.0", "request-promise": "~1.0.2", "sonos-discovery": "https://github.com/jishi/node-sonos-discovery/archive/v1.7.3.tar.gz", "wav-file-info": "0.0.8", From 69ecee8acee9fba7f1a69ef2f056df68b0bd3f92 Mon Sep 17 00:00:00 2001 From: Alex Stevenson-Price Date: Wed, 29 Jan 2025 13:59:02 +0000 Subject: [PATCH 122/123] Update README.md with slightly improved Apple Music instructions (#909) * Update README.md with improved Apple Music instructions - Added instructions on how to play playlists. - Tweaked example links to account for country code. * Fix typo --- README.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index bb0b00f9..30cb8063 100644 --- a/README.md +++ b/README.md @@ -846,6 +846,7 @@ The following endpoints are available: # Apple Music /RoomName/applemusic/{now,next,queue}/song:{songID} /RoomName/applemusic/{now,next,queue}/album:{albumID} +/RoomName/applemusic/{now,next,queue}/playlist:{playlistID} # Amazon Music /RoomName/amazonmusic/{now,next,queue}/song:{songID} @@ -872,18 +873,23 @@ It only handles a single **spotify** account currently. It will probably use the You can find **Apple Music** song and album IDs via the [iTunes Search API](https://affiliate.itunes.apple.com/resources/documentation/itunes-store-web-service-search-api/). -You can also use iTunes to figure out song and album IDs. Right click on a song or album and select "Share" -> "Copy Link". You can do this when you searched within Apple Music or from your media library as long as the song is available in Apple Music. +You can also use iTunes to figure out song, album, and playlist IDs. Right click on a song, album, or playlist and select "Share" -> "Copy Link". You can do this when you searched within Apple Music or from your media library as long as the song is available in Apple Music. Have a look at the link you just copied. *If you shared the link to a song:* -The format is: https://itunes.apple.com/de/album/{songName}/{albumID}?i={songID} +The format is: https://itunes.apple.com/{countryCode}/album/{songName}/{albumID}?i={songID} > eg: https://itunes.apple.com/de/album/blood-of-my-enemies/355363490?i=355364259 *If you shared the link to an album:* -The format is: https://itunes.apple.com/de/album/{albumName}/{albumID} +The format is: https://itunes.apple.com/{countryCode}/album/{albumName}/{albumID} > eg: https://itunes.apple.com/de/album/f-g-restless/355363490 +*If you shared the link to a playlist:* +The format is: https://itunes.apple.com/{countryCode}/playlist/{playlistName}/{playlistID} +> eg: https://music.apple.com/gb/playlist/lofi-girls-favorites/pl.ed52c9eeaa0740079c21fa8e455b225e + + **Amazon Music** To find **Amazon Music** song and album IDs you can use the Amazon Music App, search for a song or an album and share a link. From 3776f0ee2261c924c7b7204de121a38100a08ca7 Mon Sep 17 00:00:00 2001 From: Jimmy Shimizu Date: Sat, 22 Mar 2025 12:44:16 +0100 Subject: [PATCH 123/123] fix: Stop using keep-alive for http request (to match pre v20 behavior) chore: Fix deprecated new Buffer() calls --- lib/music_services/spotifyDef.js | 42 +-- lib/sonos-http-api.js | 6 +- package-lock.json | 448 ++++++++++++++++++++++++------- package.json | 7 +- 4 files changed, 380 insertions(+), 123 deletions(-) diff --git a/lib/music_services/spotifyDef.js b/lib/music_services/spotifyDef.js index d20b7216..85ba833a 100644 --- a/lib/music_services/spotifyDef.js +++ b/lib/music_services/spotifyDef.js @@ -39,7 +39,7 @@ const spotifyDef = { station: 'item.audioItem.audioBroadcast.#artistRadio', playlist:'container.playlistContainer', }, - + service: setService, term: getSearchTerm, tracks: loadTracks, @@ -50,7 +50,7 @@ const spotifyDef = { authenticate: authenticateService, } -var toBase64 = (string) => new Buffer(string).toString('base64'); +var toBase64 = (string) => Buffer.from(string).toString('base64'); const SPOTIFY_TOKEN_URL = 'https://accounts.spotify.com/api/token'; @@ -118,7 +118,7 @@ function authenticateService() { function getURI(type, id) { if (type == 'album') { return `x-rincon-cpcontainer:0004206c${id}`; - } else + } else if (type == 'song') { return `x-sonos-spotify:spotify%3atrack%3a${id}?sid=${sid}&flags=8224&sn=${accountSN}`; } else @@ -146,7 +146,7 @@ function setService(player, p_accountId, p_accountSN, p_country) sid = player.system.getServiceId('Spotify'); serviceType = player.system.getServiceType('Spotify'); accountId = p_accountId; - accountSN = 14; // GACALD: Hack to fix Spotify p_accountSN; + accountSN = 14; // GACALD: Hack to fix Spotify p_accountSN; country = p_country; } @@ -163,15 +163,15 @@ function getSearchTerm(type, term, artist, album, track) { newTerm += 'track:' + track; } newTerm = encodeURIComponent(newTerm); - + return newTerm; } - + function getMetadata(type, id, name, title) { const token = getServiceToken(); const parentUri = spotifyDef.parent[type] + name; const objectType = spotifyDef.object[type]; - + if (type != 'station') { title = ''; } @@ -194,7 +194,7 @@ function getURIandMetadata(type, resList) }; var items = []; - + if (type == 'album') { items = resList.albums.items; } else @@ -204,15 +204,15 @@ function getURIandMetadata(type, resList) if (type == 'playlist') { items = resList.playlists.items; } - + Id = items[0].id; Title = items[0].name + ((type=='station')?' Radio':''); Name = Title.toLowerCase().replace(' radio','').replace('radio ',''); MetadataID = spotifyDef.metastart[type] + encodeURIComponent(Id); - + UaM.metadata = getMetadata(type, MetadataID, (type=='album' || type=='playlist')?Title.toLowerCase() : Id, Title); UaM.uri = getURI(type, encodeURIComponent((type=='station')?items[0].id:items[0].uri)); - + return UaM; } @@ -222,18 +222,18 @@ function loadTracks(type, tracksJson) isArtist : false, queueTracks : [] }; - + if (tracksJson.tracks.items.length > 0) { // Filtered list of tracks to play tracks.queueTracks = tracksJson.tracks.items.reduce(function(tracksArray, track) { if (track.available_markets == null || track.available_markets.indexOf(country) != -1) { var skip = false; - + for (var j=0; (j < tracksArray.length) && !skip ; j++) { // Skip duplicate songs skip = (track.name == tracksArray[j].trackName); } - + if (!skip) { var metadataID = spotifyDef.metastart['song'] + encodeURIComponent(track.id); var metadata = getMetadata('song', metadataID, track.id, track.name); @@ -242,21 +242,21 @@ function loadTracks(type, tracksJson) tracksArray.push({trackName:track.name, artistName:(track.artists.length>0)?track.artists[0].name:'', uri:uri, metadata:metadata}); tracks.count++; } - } + } return tracksArray; }, []); } - + return tracks; } - + function isEmpty(type, resList) { var count = 0; if (type == 'album') { count = resList.albums.items.length; - } else + } else if (type == 'song') { count = resList.tracks.items.length; } else @@ -266,9 +266,9 @@ function isEmpty(type, resList) if (type == 'playlist') { count = resList.playlists.items.length; } - + return (count == 0); } - + module.exports = spotifyDef; - + diff --git a/lib/sonos-http-api.js b/lib/sonos-http-api.js index bc38520f..fbd09f0f 100644 --- a/lib/sonos-http-api.js +++ b/lib/sonos-http-api.js @@ -67,7 +67,7 @@ function HttpAPI(discovery, settings) { } const params = req.url.substring(1).split('/'); - + // parse decode player name considering decode errors let player; try { @@ -93,7 +93,7 @@ function HttpAPI(discovery, settings) { res.statusCode = code; res.setHeader('Content-Length', Buffer.byteLength(jsonResponse)); res.setHeader('Content-Type', 'application/json;charset=utf-8'); - res.write(new Buffer(jsonResponse)); + res.write(Buffer.from(jsonResponse)); res.end(); } @@ -142,7 +142,7 @@ function HttpAPI(discovery, settings) { if (!settings.webhook) return; - const body = new Buffer(jsonBody, 'utf8'); + const body = Buffer.from(jsonBody, 'utf8'); var headers = { 'Content-Type': 'application/json', diff --git a/package-lock.json b/package-lock.json index 2725c41a..de333814 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,9 +18,9 @@ "json5": "^0.5.1", "mime": "^1.4.1", "music-metadata": "^1.1.0", - "node-static": "^0.7.9", "request-promise": "~1.0.2", - "sonos-discovery": "https://github.com/jishi/node-sonos-discovery/archive/v1.7.3.tar.gz", + "serve-static": "^1.15.0", + "sonos-discovery": "https://github.com/jishi/node-sonos-discovery/archive/v1.8.0.tar.gz", "wav-file-info": "0.0.8" }, "devDependencies": { @@ -29,8 +29,7 @@ "eslint-plugin-import": "^2.7.0" }, "engines": { - "node": ">=4.0.0", - "npm": "^2.0.0" + "node": ">=4 <23" } }, "node_modules/acorn": { @@ -526,14 +525,6 @@ "integrity": "sha1-SxQVMEz1ACjqgWQ2Q72C6gWANok=", "dev": true }, - "node_modules/colors": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.3.1.tgz", - "integrity": "sha512-jg/vxRmv430jixZrC+La5kMbUWqIg32/JsYNZb94+JEmzceYbWKTsv1OuTp+7EaqiaWRR2tPcykibwCRgclIsw==", - "engines": { - "node": ">=0.1.90" - } - }, "node_modules/combined-stream": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", @@ -642,6 +633,25 @@ "node": ">=0.4.0" } }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, "node_modules/doctrine": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", @@ -664,6 +674,12 @@ "safer-buffer": "^2.1.0" } }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, "node_modules/elevenlabs-node": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/elevenlabs-node/-/elevenlabs-node-2.0.1.tgz", @@ -705,6 +721,15 @@ "node": ">= 10.0.0" } }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -719,6 +744,12 @@ "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, "node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -979,6 +1010,15 @@ "node": ">=0.10.0" } }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/events": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", @@ -1131,6 +1171,15 @@ "node": ">= 0.12" } }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/fs-extra": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-6.0.1.tgz", @@ -1338,6 +1387,22 @@ "node >= 0.4.0" ] }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/http-signature": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", @@ -1395,9 +1460,10 @@ } }, "node_modules/inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" }, "node_modules/inquirer": { "version": "3.3.0", @@ -1781,11 +1847,6 @@ "node": "*" } }, - "node_modules/minimist": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", - "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=" - }, "node_modules/mkdirp": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", @@ -1843,22 +1904,6 @@ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, - "node_modules/node-static": { - "version": "0.7.10", - "resolved": "https://registry.npmjs.org/node-static/-/node-static-0.7.10.tgz", - "integrity": "sha512-bd7zO5hvCWzdglgwz9t82T4mYTEUzEG5pXnSqEzitvmEacusbhl8/VwuCbMaYR9g2PNK5191yBtAEQLJEmQh1A==", - "dependencies": { - "colors": ">=0.6.0", - "mime": "^1.2.9", - "optimist": ">=0.3.4" - }, - "bin": { - "static": "bin/cli.js" - }, - "engines": { - "node": ">= 0.4.1" - } - }, "node_modules/normalize-package-data": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", @@ -1888,6 +1933,18 @@ "node": ">=0.10.0" } }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -1909,15 +1966,6 @@ "node": ">=4" } }, - "node_modules/optimist": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", - "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", - "dependencies": { - "minimist": "~0.0.1", - "wordwrap": "~0.0.2" - } - }, "node_modules/optionator": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", @@ -1995,6 +2043,15 @@ "node": ">=0.10.0" } }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/path-exists": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", @@ -2153,6 +2210,15 @@ "node": ">=0.4.x" } }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/read-pkg": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", @@ -2374,6 +2440,81 @@ "semver": "bin/semver" } }, + "node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, "node_modules/shebang-command": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", @@ -2419,12 +2560,15 @@ } }, "node_modules/sonos-discovery": { - "version": "1.7.3", - "resolved": "https://github.com/jishi/node-sonos-discovery/archive/v1.7.3.tar.gz", - "integrity": "sha512-bHog/7+zjirplyZOjclSM9g6rZVZX0bDWX+PzkS6KN0I1/KK6SjotazhkI4nhcrx4NYWnxCQULIO9RvzlyG2uA==", + "version": "1.8.0", + "resolved": "https://github.com/jishi/node-sonos-discovery/archive/v1.8.0.tar.gz", + "integrity": "sha512-ZDJoW3kcDYMEfGwaxyyk1Tg+Max/FewyLU4YEKaEhBrf7df3eAdrhJADRsZy5UmmKT/ZxavMiShx1IUjE/PYUw==", "dependencies": { "html-entities": "1.0.x", "xml-flow": "1.0.2" + }, + "engines": { + "node": ">=4 <23" } }, "node_modules/sonos-discovery/node_modules/html-entities": { @@ -2499,6 +2643,15 @@ "tweetnacl": "~0.14.0" } }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -2638,6 +2791,15 @@ "node": ">=0.6.0" } }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, "node_modules/token-types": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/token-types/-/token-types-0.9.4.tgz", @@ -2805,14 +2967,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/wordwrap": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", - "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -3264,11 +3418,6 @@ "integrity": "sha1-SxQVMEz1ACjqgWQ2Q72C6gWANok=", "dev": true }, - "colors": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.3.1.tgz", - "integrity": "sha512-jg/vxRmv430jixZrC+La5kMbUWqIg32/JsYNZb94+JEmzceYbWKTsv1OuTp+7EaqiaWRR2tPcykibwCRgclIsw==" - }, "combined-stream": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", @@ -3359,6 +3508,16 @@ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" }, + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + }, + "destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" + }, "doctrine": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", @@ -3378,6 +3537,11 @@ "safer-buffer": "^2.1.0" } }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, "elevenlabs-node": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/elevenlabs-node/-/elevenlabs-node-2.0.1.tgz", @@ -3413,6 +3577,11 @@ } } }, + "encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==" + }, "error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -3427,6 +3596,11 @@ "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -3637,6 +3811,11 @@ "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", "dev": true }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" + }, "events": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", @@ -3748,6 +3927,11 @@ "mime-types": "^2.1.12" } }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==" + }, "fs-extra": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-6.0.1.tgz", @@ -3906,6 +4090,18 @@ "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.2.1.tgz", "integrity": "sha1-DfKTUfByEWNRXfueVUPl9u7VFi8=" }, + "http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "requires": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + } + }, "http-signature": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", @@ -3953,9 +4149,9 @@ } }, "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "inquirer": { "version": "3.3.0", @@ -4257,11 +4453,6 @@ "brace-expansion": "^1.1.7" } }, - "minimist": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", - "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=" - }, "mkdirp": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", @@ -4316,16 +4507,6 @@ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, - "node-static": { - "version": "0.7.10", - "resolved": "https://registry.npmjs.org/node-static/-/node-static-0.7.10.tgz", - "integrity": "sha512-bd7zO5hvCWzdglgwz9t82T4mYTEUzEG5pXnSqEzitvmEacusbhl8/VwuCbMaYR9g2PNK5191yBtAEQLJEmQh1A==", - "requires": { - "colors": ">=0.6.0", - "mime": "^1.2.9", - "optimist": ">=0.3.4" - } - }, "normalize-package-data": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", @@ -4349,6 +4530,14 @@ "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", "dev": true }, + "on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "requires": { + "ee-first": "1.1.1" + } + }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -4367,15 +4556,6 @@ "mimic-fn": "^1.0.0" } }, - "optimist": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", - "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", - "requires": { - "minimist": "~0.0.1", - "wordwrap": "~0.0.2" - } - }, "optionator": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", @@ -4437,6 +4617,11 @@ "error-ex": "^1.2.0" } }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" + }, "path-exists": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", @@ -4558,6 +4743,11 @@ "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" }, + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" + }, "read-pkg": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", @@ -4745,6 +4935,69 @@ "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", "dev": true }, + "send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "requires": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + } + } + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + } + } + }, + "serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "requires": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + } + }, + "setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, "shebang-command": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", @@ -4781,8 +5034,8 @@ } }, "sonos-discovery": { - "version": "https://github.com/jishi/node-sonos-discovery/archive/v1.7.3.tar.gz", - "integrity": "sha512-bHog/7+zjirplyZOjclSM9g6rZVZX0bDWX+PzkS6KN0I1/KK6SjotazhkI4nhcrx4NYWnxCQULIO9RvzlyG2uA==", + "version": "https://github.com/jishi/node-sonos-discovery/archive/v1.8.0.tar.gz", + "integrity": "sha512-ZDJoW3kcDYMEfGwaxyyk1Tg+Max/FewyLU4YEKaEhBrf7df3eAdrhJADRsZy5UmmKT/ZxavMiShx1IUjE/PYUw==", "requires": { "html-entities": "1.0.x", "xml-flow": "1.0.2" @@ -4849,6 +5102,11 @@ "tweetnacl": "~0.14.0" } }, + "statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" + }, "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -4961,6 +5219,11 @@ "os-tmpdir": "~1.0.2" } }, + "toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" + }, "token-types": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/token-types/-/token-types-0.9.4.tgz", @@ -5099,11 +5362,6 @@ "is-typed-array": "^1.1.10" } }, - "wordwrap": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", - "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=" - }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", diff --git a/package.json b/package.json index 5730bd53..aa8326c2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sonos-http-api", - "version": "1.6.9", + "version": "1.7.0", "description": "A simple node app for controlling a Sonos system with basic HTTP requests", "scripts": { "start": "node server.js", @@ -22,13 +22,12 @@ "music-metadata": "^1.1.0", "serve-static": "^1.15.0", "request-promise": "~1.0.2", - "sonos-discovery": "https://github.com/jishi/node-sonos-discovery/archive/v1.7.3.tar.gz", + "sonos-discovery": "https://github.com/jishi/node-sonos-discovery/archive/v1.8.0.tar.gz", "wav-file-info": "0.0.8", "elevenlabs-node": "2.0.1" }, "engines": { - "node": ">=4.0.0", - "npm": "^2.0.0" + "node": ">=4 <23" }, "main": "lib/sonos-http-api.js", "license": "MIT",