diff --git a/.gitignore b/.gitignore index eb03e3e..ac1e8f7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ node_modules *.log +.DS_Store diff --git a/app.js b/app.js index e7b49a4..cb6f82d 100755 --- a/app.js +++ b/app.js @@ -1,30 +1,30 @@ -var fs = require('fs') -var path = require('path') -var util = require('util') -var express = require('express') -var morgan = require('morgan') -var bodyParser = require('body-parser') -var iTunes = require('local-itunes') -var osa = require('osa') -var osascript = require('osascript') -var airplay = require('./lib/airplay') -var parameterize = require('parameterize'); - -var app = express() -app.use(bodyParser.urlencoded({ extended: false })) +const path = require('path'); +const util = require('util'); +const express = require('express'); +const morgan = require('morgan'); +const bodyParser = require('body-parser'); +const iTunes = require('local-itunes'); +const osa = require('osa'); +const osascript = require('osascript'); +const airplay = require('./lib/airplay'); +const metadata = require('./lib/metadata'); +const parameterize = require('parameterize'); + +const app = express(); +app.use(bodyParser.urlencoded({ extended: false })); app.use(express.static(path.join(__dirname, 'public'))); -var logFormat = "'[:date[iso]] - :remote-addr - :method :url :status :response-time ms - :res[content-length]b'" -app.use(morgan(logFormat)) +const logFormat = "[:date[iso]] - :remote-addr - :method :url :status :response-time ms - :res[content-length]b'"; +app.use(morgan(logFormat)); -function getCurrentState(){ +function getCurrentState() { itunes = Application('iTunes'); playerState = itunes.playerState(); currentState = {}; currentState['player_state'] = playerState; - if (playerState != "stopped") { + if (playerState != 'stopped') { currentTrack = itunes.currentTrack; currentPlaylist = itunes.currentPlaylist; @@ -39,104 +39,133 @@ function getCurrentState(){ currentState['shuffle'] = itunes.shuffleEnabled() && itunes.shuffleMode(); if (currentTrack.year()) { - currentState['album'] += " (" + currentTrack.year() + ")"; + currentState['album'] += ' (' + currentTrack.year() + ')'; } } return currentState; } -function sendResponse(error, res){ +function sendResponse(error, res) { if (error) { - console.log(error) - res.sendStatus(500) - }else{ + console.log(error); + res.sendStatus(500); + } else { osa(getCurrentState, function (error, state) { if (error) { - console.log(error) - res.sendStatus(500) - }else{ - res.json(state) + console.log(error); + res.sendStatus(500); + } else { + res.json(state); } - }) + }); } } -function playPlaylist(nameOrId){ +function playPlaylist(nameOrId) { itunes = Application('iTunes'); if ((nameOrId - 0) == nameOrId && ('' + nameOrId).trim().length > 0) { id = parseInt(nameOrId); itunes.playlists.byId(id).play(); - }else{ + } else { itunes.playlists.byName(nameOrId).play(); } return true; } -function setVolume(level){ +function shufflePlaylist(nameOrId) { + itunes = Application('iTunes'); + + if ((nameOrId - 0) == nameOrId && ('' + nameOrId).trim().length > 0) { + id = parseInt(nameOrId); + itunes.playlists.byId(id).play(); + } else { + itunes.playlists.byName(nameOrId).play(); + } + + itunes.shuffleEnabled = true; + itunes.shuffleMode = 'songs'; + + return true; +} + +function playFilteredMusic(filter_type, name, callback) { + const options = { args: [filter_type, name] }; + osascript.file(path.join(__dirname, 'lib/playFilteredMusic.applescript'), options, function (err, data) { + callback(err, data); + }); + + return true; +} + +function getVolume() { + itunes = Application('iTunes'); + return {'volume': itunes.soundVolume()}; +} + +function setVolume(level) { itunes = Application('iTunes'); if (level) { itunes.soundVolume = parseInt(level); return true; - }else { + } else { return false; } } -function setMuted(muted){ +function setMuted(muted) { itunes = Application('iTunes'); if (muted) { itunes.mute = muted; return true; - }else{ + } else { return false; } } -function setShuffle(mode){ +function setShuffle(mode) { itunes = Application('iTunes'); if (!mode) { - mode = "songs" + mode = 'songs'; } - if (mode == "false" || mode == "off") { + if (mode == 'false' || mode == 'off') { itunes.shuffleEnabled = false; return false; - }else{ + } else { itunes.shuffleEnabled = true; itunes.shuffleMode = mode; return true; } } -function setRepeat(mode){ +function setRepeat(mode) { itunes = Application('iTunes'); if (!mode) { - mode = "all" + mode = 'all'; } - if (mode == "false" || mode == "off") { + if (mode == 'false' || mode == 'off') { itunes.songRepeat = false; return false; - }else{ + } else { itunes.songRepeat = mode; return true; } } -function getPlaylistsFromItunes(){ +function getPlaylistsFromItunes() { itunes = Application('iTunes'); playlists = itunes.playlists(); - playlistNames = []; - for (var i = 0; i < playlists.length; i++) { + for (let i = 0; i < playlists.length; i++) { playlist = playlists[i]; data = {}; @@ -151,208 +180,325 @@ function getPlaylistsFromItunes(){ return playlistNames; } -function getPlaylists(callback){ +function getPlaylists(callback) { osa(getPlaylistsFromItunes, function (error, data) { - if (error){ - callback(error) - }else{ - for (var i = 0; i < data.length; i++) { - data[i]['id'] = parameterize(data[i]['name']) + if (error) { + callback(error); + } else { + for (let i = 0; i < data.length; i++) { + data[i]['id'] = parameterize(data[i]['name']); } - callback(null, data) + callback(null, data); } - }) + }); } -app.get('/_ping', function(req, res){ +function refreshNowPlayingPlaylist(callback) { + osascript.file(path.join(__dirname, 'lib/refreshNowPlayingPlaylist.applescript'), function (err, data) { + callback(err, data); + }); + + return true; +} + +app.get('/_ping', function (req, res) { res.send('OK'); -}) +}); -app.get('/', function(req, res){ +app.get('/', function (req, res) { res.sendfile('index.html'); -}) - -app.put('/play', function(req, res){ - iTunes.play(function (error){ - sendResponse(error, res) - }) -}) - -app.put('/pause', function(req, res){ - iTunes.pause(function (error){ - sendResponse(error, res) - }) -}) - -app.put('/playpause', function(req, res){ - iTunes.playpause(function (error){ - sendResponse(error, res) - }) -}) - -app.put('/stop', function(req, res){ - iTunes.stop(function (error){ - sendResponse(error, res) - }) -}) - -app.put('/previous', function(req, res){ - iTunes.previous(function (error){ - sendResponse(error, res) - }) -}) - -app.put('/next', function(req, res){ - iTunes.next(function (error){ - sendResponse(error, res) - }) -}) - -app.put('/volume', function(req, res){ - osa(setVolume, req.body.level, function(error, data, log){ - if (error){ - console.log(error) - res.sendStatus(500) - }else{ - sendResponse(error, res) +}); + +app.put('/play', function (req, res) { + iTunes.play(function (error) { + sendResponse(error, res); + }); +}); + +app.put('/pause', function (req, res) { + iTunes.pause(function (error) { + sendResponse(error, res); + }); +}); + +app.put('/playpause', function (req, res) { + iTunes.playpause(function (error) { + sendResponse(error, res); + }); +}); + +app.put('/stop', function (req, res) { + iTunes.stop(function (error) { + sendResponse(error, res); + }); +}); + +app.put('/previous', function (req, res) { + iTunes.previous(function (error) { + sendResponse(error, res); + }); +}); + +app.put('/next', function (req, res) { + iTunes.next(function (error) { + sendResponse(error, res); + }); +}); + +app.get('/volume', function(req, res) { + osa(getVolume, function (error, data) { + if (error) { + console.log(error); + res.sendStatus(500); + } else { + res.json(data); + } + }); +}); + +app.put('/volume', function (req, res) { + osa(setVolume, req.body.level, function (error, data, log) { + if (error) { + console.log(error); + res.sendStatus(500); + } else { + sendResponse(error, res); } - }) -}) - -app.put('/mute', function(req, res){ - osa(setMuted, req.body.muted, function(error, data, log){ - if (error){ - console.log(error) - res.sendStatus(500) - }else{ - sendResponse(error, res) + }); +}); + +app.put('/mute', function (req, res) { + osa(setMuted, req.body.muted, function (error, data, log) { + if (error) { + console.log(error); + res.sendStatus(500); + } else { + sendResponse(error, res); } - }) -}) - -app.put('/shuffle', function(req, res){ - osa(setShuffle, req.body.mode, function(error, data, log){ - if (error){ - console.log(error) - res.sendStatus(500) - }else{ - sendResponse(error, res) + }); +}); + +app.put('/shuffle', function (req, res) { + osa(setShuffle, req.body.mode, function (error, data, log) { + if (error) { + console.log(error); + res.sendStatus(500); + } else { + sendResponse(error, res); } - }) -}) - -app.put('/repeat', function(req, res){ - osa(setRepeat, req.body.mode, function(error, data, log){ - if (error){ - console.log(error) - res.sendStatus(500) - }else{ - sendResponse(error, res) + }); +}); + +app.put('/repeat', function (req, res) { + osa(setRepeat, req.body.mode, function (error, data, log) { + if (error) { + console.log(error); + res.sendStatus(500); + } else { + sendResponse(error, res); } - }) -}) + }); +}); -app.get('/now_playing', function(req, res){ - error = null - sendResponse(error, res) -}) +app.get('/now_playing', function (req, res) { + error = null; + sendResponse(error, res); +}); -app.get('/artwork', function(req, res){ +app.get('/artwork', function (req, res) { osascript.file(path.join(__dirname, 'lib', 'art.applescript'), function (error, data) { - res.type('image/jpeg') - res.sendFile('/tmp/currently-playing.jpg') - }) -}) + res.type('image/jpeg'); + res.sendFile('/tmp/currently-playing.jpg'); + }); +}); app.get('/playlists', function (req, res) { getPlaylists(function (error, data) { - if (error){ - console.log(error) - res.sendStatus(500) - }else{ - res.json({playlists: data}) + if (error) { + console.log(error); + res.sendStatus(500); + } else { + res.json({ playlists: data }); } - }) -}) + }); +}); app.put('/playlists/:id/play', function (req, res) { osa(getPlaylistsFromItunes, function (error, data) { - if (error){ - res.sendStatus(500) - }else{ - for (var i = 0; i < data.length; i++) { - playlist = data[i] + if (error) { + res.sendStatus(500); + } else { + for (let i = 0; i < data.length; i++) { + playlist = data[i]; if (req.params.id == parameterize(playlist['name'])) { osa(playPlaylist, playlist['id'], function (error, data) { - sendResponse(error, res) - }) - return + sendResponse(error, res); + }); + + return; } } - res.sendStatus(404) + + res.sendStatus(404); } - }) + }); +}); -}) +app.put('/playlists/:id/shuffle', function (req, res) { + osa(getPlaylistsFromItunes, function (error, data) { + if (error) { + res.sendStatus(500); + } else { + for (let i = 0; i < data.length; i++) { + playlist = data[i]; + if (req.params.id == parameterize(playlist['name'])) { + osa(shufflePlaylist, playlist['id'], function (error, data) { + sendResponse(error, res); + }); -app.get('/airplay_devices', function(req, res){ - osa(airplay.listAirPlayDevices, function(error, data, log){ - if (error){ - res.sendStatus(500) - }else{ - res.json({'airplay_devices': data}) + return; + } + } + res.sendStatus(404); + } + }); +}); + +app.get('/airplay_devices', function (req, res) { + osa(airplay.listAirPlayDevices, function (error, data, log) { + if (error) { + res.sendStatus(500); + } else { + res.json({ 'airplay_devices': data }); } - }) -}) - -app.get('/airplay_devices/:id', function(req, res){ - osa(airplay.listAirPlayDevices, function(error, data, log){ - if (error){ - res.sendStatus(500) - }else{ - for (var i = 0; i < data.length; i++) { - device = data[i] + }); +}); + +app.get('/airplay_devices/:id', function (req, res) { + osa(airplay.listAirPlayDevices, function (error, data, log) { + if (error) { + res.sendStatus(500); + } else { + for (let i = 0; i < data.length; i++) { + device = data[i]; if (req.params.id == device['id']) { - res.json(device) - return + res.json(device); + return; } } - res.sendStatus(404) + + res.sendStatus(404); } - }) -}) + }); +}); app.put('/airplay_devices/:id/on', function (req, res) { - osa(airplay.setSelectionStateAirPlayDevice, req.params.id, true, function(error, data, log){ - if (error){ - console.log(error) - res.sendStatus(500) - }else{ - res.json(data) + osa(airplay.setSelectionStateAirPlayDevice, req.params.id, true, function (error, data, log) { + if (error) { + console.log(error); + res.sendStatus(500); + } else { + res.json(data); } - }) -}) + }); +}); app.put('/airplay_devices/:id/off', function (req, res) { - osa(airplay.setSelectionStateAirPlayDevice, req.params.id, false, function(error, data, log){ - if (error){ - console.log(error) - res.sendStatus(500) - }else{ - res.json(data) + osa(airplay.setSelectionStateAirPlayDevice, req.params.id, false, function (error, data, log) { + if (error) { + console.log(error); + res.sendStatus(500); + } else { + res.json(data); } - }) -}) + }); +}); app.put('/airplay_devices/:id/volume', function (req, res) { - osa(airplay.setVolumeAirPlayDevice, req.params.id, req.body.level, function(error, data, log){ - if (error){ - console.log(error) - res.sendStatus(500) - }else{ - res.json(data) + osa(airplay.setVolumeAirPlayDevice, req.params.id, req.body.level, function (error, data, log) { + if (error) { + console.log(error); + res.sendStatus(500); + } else { + res.json(data); + } + }); +}); + +app.get('/artists', function (req, res) { + metadata.getArtists(function (error, data) { + if (error) { + console.log(error); + res.sendStatus(500); + } else { + res.json({ 'artists': data }); + } + }); +}); + +app.put('/artists/update', function (req, res) { + metadata.updateArtists(function (error, data) { + if (error) { + console.log(error); + res.sendStatus(500); + } else { + res.json(data); } - }) -}) + }); +}); + +app.put('/artists/:name/play', function (req, res) { + const artist = req.params.name; + refreshNowPlayingPlaylist(function (error, data) { + if (error) { + console.log(error); + res.sendStatus(500); + } else { + playFilteredMusic('artist', artist, function (error, data) { + sendResponse(error, res); + }); + } + }); + + return; +}); + +app.get('/genres', function (req, res) { + metadata.getGenres(function (error, data) { + if (error) { + console.log(error); + res.sendStatus(500); + } else { + res.json({ 'genres': data }); + } + }); +}); + +app.put('/genres/update', function (req, res) { + metadata.updateGenres(function (error, data) { + if (error) { + console.log(error); + res.sendStatus(500); + } else { + res.json(data); + } + }); +}); + +app.put('/genres/:name/play', function (req, res) { + const genre = req.params.name; + refreshNowPlayingPlaylist(function (error, data) { + if (error) { + console.log(error); + res.sendStatus(500); + } else { + playFilteredMusic('genre', genre, function (error, data) { + sendResponse(error, res); + }); + } + }); + + return; +}); app.listen(process.env.PORT || 8181); diff --git a/lib/airplay.js b/lib/airplay.js index 3c16c54..6ad2bbc 100644 --- a/lib/airplay.js +++ b/lib/airplay.js @@ -1,9 +1,10 @@ module.exports = { - listAirPlayDevices: function (callback){ + listAirPlayDevices: function(callback) { itunes = Application('iTunes'); airPlayDevices = itunes.airplayDevices(); airPlayResults = []; + for (i = 0; i < airPlayDevices.length; i++) { airPlayDevice = airPlayDevices[i]; @@ -13,6 +14,7 @@ module.exports = { } else { deviceData["id"] = airPlayDevice.name(); } + deviceData["name"] = airPlayDevice.name(); deviceData["kind"] = airPlayDevice.kind(); deviceData["active"] = airPlayDevice.active(); @@ -27,7 +29,7 @@ module.exports = { return airPlayResults; }, - setSelectionStateAirPlayDevice: function (id, selected){ + setSelectionStateAirPlayDevice: function(id, selected) { itunes = Application('iTunes'); id = id.replace(/-/g, ':'); foundAirPlayDevice = null; @@ -63,7 +65,7 @@ module.exports = { return deviceData; }, - setVolumeAirPlayDevice: function (id, level){ + setVolumeAirPlayDevice: function(id, level) { itunes = Application('iTunes'); id = id.replace(/-/g, ':'); foundAirPlayDevice = null; @@ -98,4 +100,4 @@ module.exports = { return deviceData; } -} +}; diff --git a/lib/metadata.applescript b/lib/metadata.applescript new file mode 100644 index 0000000..c3c563f --- /dev/null +++ b/lib/metadata.applescript @@ -0,0 +1,70 @@ +on run argv + if (count of argv) > 0 then + set update_type to item 1 of argv + tell application "iTunes" + if update_type = "artists" then + set list_manager's unfiltered_artists to (get album artist of (tracks whose EQ is not "Spoken Word" and genre is not "Podcast" and genre is not "Audiobook" and genre is not "News" and compilation is false and album artist is not "")) + set tempFilePath to "/tmp/artists.txt" + else if update_type = "genres" then + set list_manager's unfiltered_genres to (get genre of (tracks whose EQ is not "Spoken Word" and genre is not "Podcast" and genre is not "Audiobook" and genre is not "News" and genre is not "")) + set tempFilePath to "/tmp/genres.txt" + end if + + set unique_data to my filter_data(update_type) + my write_to_file(unique_data, tempFilePath, false) + end tell + end if +end run + +script list_manager + property unfiltered_artists : "" + property unfiltered_genres : "" +end script + +on filter_data(filter_type) + set filtered_data to {} + + if filter_type is "artists" then + repeat with i from 1 to count list_manager's unfiltered_artists + set current_item to item i of list_manager's unfiltered_artists + if current_item is not in filtered_data then + set end of filtered_data to current_item + end if + end repeat + else if filter_type is "genres" then + repeat with i from 1 to count list_manager's unfiltered_genres + set current_item to item i of list_manager's unfiltered_genres + if current_item is not in filtered_data then + set end of filtered_data to current_item + end if + end repeat + end if + + return filtered_data +end filter_data + +on write_to_file(this_data, target_file, append_data) + try + set full_text to "" + + repeat with i from 1 to number of items in this_data + set this_item to item i of this_data + set full_text to full_text & this_item + if i is not number of items in this_data then set full_text to full_text & "\n" + end repeat + + set the target_file to the target_file as string + set the open_target_file to open for access target_file with write permission + + if append_data is false then set eof of the open_target_file to 0 + write full_text to the open_target_file starting at eof as Çclass utf8È + close access the open_target_file + + return true + on error + try + close access file target_file + end try + return false + end try +end write_to_file \ No newline at end of file diff --git a/lib/metadata.js b/lib/metadata.js new file mode 100644 index 0000000..58e9e66 --- /dev/null +++ b/lib/metadata.js @@ -0,0 +1,112 @@ +/*jshint esversion: 6 */ + +var osa = require('osa'); +var osascript = require('osascript'); +var fs = require('fs'); +var path = require('path'); + +var artists = loadMetadata('artists'); +var genres = loadMetadata('genres'); + +function ignoreCase(a, b) { + a = a.replace(/^The /i, ''); + b = b.replace(/^The /i, ''); + return a.localeCompare(b, 'en', {'sensitivity': 'base'}); +} + +function resetMetadata(type) { + if (type == 'artists') { + artists = []; + } else if (type == 'genres') { + genres = []; + } +} + +function loadMetadata(type, callback) { + var cacheFilePath = '/tmp/' + type + '.txt'; + + fs.stat(cacheFilePath, function (error, stats) { + if (error) { + resetMetadata(type); + updateMetadata(type, readMetadataFile(type, cacheFilePath, callback)); + } else { + readMetadataFile(type, cacheFilePath,callback); + } + }); +} + +function readMetadataFile(type, filePath, callback) { + fs.readFile(filePath, 'utf8', function (error, contents) { + if (error) { + resetMetadata(type); + if (callback) callback(error); + } else { + var parsedMetadata = contents.toString().split('\n').sort(ignoreCase); + + if (type == 'artists') { + artists = parsedMetadata; + } else if (type == 'genres') { + genres = parsedMetadata; + } + + if (callback) callback(null, parsedMetadata); + } + }); +} + +function updateMetadata(type, callback) { + var options = { + type: 'AppleScript', + args: [type] + }; + + osascript.file(path.join(__dirname, 'metadata.applescript'), options, function (error, data) { + if (error) { + console.log(error); + } else { + console.log(type + " metadata updated"); + } + + if (callback) callback(); + }); +} + +module.exports = { + getArtists: function(callback) { + if (!artists || artists.length == 0) { + loadMetadata('artists', function (error, data) { + if (error) { + callback(error); + } else { + callback(null, data); + } + }); + } else { + if (callback) callback(null, artists); + } + }, + + getGenres: function(callback) { + if (!genres || genres.length == 0) { + loadMetadata('genres', function (error, data) { + if (error) { + callback(error); + } else { + callback(null, data); + } + }); + } else { + if (callback) callback(null, genres); + } + }, + + updateArtists: function(callback) { + updateMetadata('artists'); + callback(null, true); + }, + + updateGenres: function(callback) { + updateMetadata('genres'); + callback(null, true); + } +}; \ No newline at end of file diff --git a/lib/playFilteredMusic.applescript b/lib/playFilteredMusic.applescript new file mode 100644 index 0000000..3dd1f43 --- /dev/null +++ b/lib/playFilteredMusic.applescript @@ -0,0 +1,22 @@ +on run argv + if (count of argv) > 0 then + set filter_type to item 1 of argv + set filter_query to item 2 of argv + + tell application "iTunes" + set nowPlayingPlaylist to playlist "Now Playing - API" + + if filter_type is equal to "genre" then + duplicate (shared tracks of playlist "3+ Stars" whose genre is filter_query) to nowPlayingPlaylist + duplicate (shared tracks of playlist "Unrated" whose genre is filter_query) to nowPlayingPlaylist + else if filter_type is equal to "artist" then + duplicate (shared tracks of playlist "3+ Stars" whose artist contains filter_query) to nowPlayingPlaylist + duplicate (shared tracks of playlist "Unrated" whose artist contains filter_query) to nowPlayingPlaylist + end if + + play nowPlayingPlaylist + set shuffle enabled to true + set shuffle mode to songs + end tell + end if +end run \ No newline at end of file diff --git a/lib/refreshNowPlayingPlaylist.applescript b/lib/refreshNowPlayingPlaylist.applescript new file mode 100644 index 0000000..11789f9 --- /dev/null +++ b/lib/refreshNowPlayingPlaylist.applescript @@ -0,0 +1,9 @@ +tell application "iTunes" + set playlist_name to "Now Playing - API" + + if not (exists playlist playlist_name) then + make new playlist with properties {name:playlist_name} + else + delete tracks of playlist playlist_name + end if +end tell \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..852c00d --- /dev/null +++ b/package-lock.json @@ -0,0 +1,405 @@ +{ + "name": "itunes-api", + "version": "1.5.2", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "accepts": { + "version": "https://registry.npmjs.org/accepts/-/accepts-1.3.4.tgz", + "integrity": "sha1-hiRnWMfdbSGmR0/whKR0DsBesh8=", + "requires": { + "mime-types": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz", + "negotiator": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz" + } + }, + "array-flatten": { + "version": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + }, + "basic-auth": { + "version": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.0.tgz", + "integrity": "sha1-AV2z81PgLlY3d1X5YnQuiYHnu7o=", + "requires": { + "safe-buffer": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz" + } + }, + "body-parser": { + "version": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz", + "integrity": "sha1-h2eKGdhLR9hZuDGZvVm84iKxBFQ=", + "requires": { + "bytes": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "content-type": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "debug": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "depd": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", + "http-errors": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", + "iconv-lite": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", + "on-finished": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "qs": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", + "raw-body": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz", + "type-is": "https://registry.npmjs.org/type-is/-/type-is-1.6.15.tgz" + } + }, + "bytes": { + "version": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" + }, + "content-disposition": { + "version": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", + "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=" + }, + "content-type": { + "version": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha1-4TjMdeBAxyexlm/l5fjJruJW/js=" + }, + "cookie": { + "version": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", + "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" + }, + "cookie-signature": { + "version": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + }, + "debug": { + "version": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha1-XRKFFd8TT/Mn6QpMk/Tgd6U2NB8=", + "requires": { + "ms": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz" + } + }, + "depd": { + "version": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", + "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=" + }, + "destroy": { + "version": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + }, + "duplex-child-process": { + "version": "https://registry.npmjs.org/duplex-child-process/-/duplex-child-process-0.0.5.tgz", + "integrity": "sha1-HCRiuOyP0B5DKDbrvVkrzf+7Zek=" + }, + "ee-first": { + "version": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, + "encodeurl": { + "version": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.1.tgz", + "integrity": "sha1-eePVhlU0aQn+bw9Fpd5oEDspTSA=" + }, + "escape-html": { + "version": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, + "etag": { + "version": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" + }, + "express": { + "version": "https://registry.npmjs.org/express/-/express-4.16.2.tgz", + "integrity": "sha1-41xt/i1kt9ygpc1PIXgb4ymeB2w=", + "requires": { + "accepts": "https://registry.npmjs.org/accepts/-/accepts-1.3.4.tgz", + "array-flatten": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "body-parser": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz", + "content-disposition": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", + "content-type": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "cookie": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", + "cookie-signature": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "debug": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "depd": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", + "encodeurl": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.1.tgz", + "escape-html": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "etag": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "finalhandler": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.0.tgz", + "fresh": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "merge-descriptors": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "methods": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "on-finished": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "parseurl": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", + "path-to-regexp": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "proxy-addr": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.2.tgz", + "qs": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", + "range-parser": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", + "safe-buffer": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", + "send": "https://registry.npmjs.org/send/-/send-0.16.1.tgz", + "serve-static": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.1.tgz", + "setprototypeof": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "statuses": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", + "type-is": "https://registry.npmjs.org/type-is/-/type-is-1.6.15.tgz", + "utils-merge": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "vary": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz" + }, + "dependencies": { + "setprototypeof": { + "version": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha1-0L2FU2iHtv58DYGMuWLZ2RxU5lY=" + }, + "statuses": { + "version": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", + "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=" + } + } + }, + "file-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/file-match/-/file-match-1.0.2.tgz", + "integrity": "sha1-ycrSZdLIrfOoFHWw30dYWQafrvc=", + "requires": { + "utils-extend": "1.0.8" + } + }, + "file-system": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/file-system/-/file-system-2.2.2.tgz", + "integrity": "sha1-fWWDPjojR9zZVqgTxncVPtPt2Yc=", + "requires": { + "file-match": "1.0.2", + "utils-extend": "1.0.8" + } + }, + "finalhandler": { + "version": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.0.tgz", + "integrity": "sha1-zgtoVbRYU+eRsvzGgARtiCU91/U=", + "requires": { + "debug": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "encodeurl": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.1.tgz", + "escape-html": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "on-finished": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "parseurl": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", + "statuses": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", + "unpipe": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz" + }, + "dependencies": { + "statuses": { + "version": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", + "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=" + } + } + }, + "forwarded": { + "version": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", + "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" + }, + "fresh": { + "version": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" + }, + "http-errors": { + "version": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", + "integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=", + "requires": { + "depd": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", + "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "setprototypeof": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", + "statuses": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz" + } + }, + "iconv-lite": { + "version": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", + "integrity": "sha1-90aPYBNfXl2tM5nAqBvpoWA6CCs=" + }, + "inherits": { + "version": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "ipaddr.js": { + "version": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.5.2.tgz", + "integrity": "sha1-1LUFvemUaYfM8PxY2QEP+WB+P6A=" + }, + "local-itunes": { + "version": "https://registry.npmjs.org/local-itunes/-/local-itunes-0.3.0.tgz", + "integrity": "sha1-mDmaRag1BlcI84nhU0zRoGQNCAY=", + "requires": { + "neo-async": "https://registry.npmjs.org/neo-async/-/neo-async-1.8.2.tgz", + "osascript": "https://registry.npmjs.org/osascript/-/osascript-1.2.0.tgz", + "require-dir": "https://registry.npmjs.org/require-dir/-/require-dir-0.3.2.tgz" + } + }, + "media-typer": { + "version": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" + }, + "merge-descriptors": { + "version": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + }, + "methods": { + "version": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" + }, + "mime": { + "version": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", + "integrity": "sha1-Eh+evEnjdm8xGnbh+hyAA8SwOqY=" + }, + "mime-db": { + "version": "https://registry.npmjs.org/mime-db/-/mime-db-1.30.0.tgz", + "integrity": "sha1-dMZD2i3Z1qRTmZY0ZbJtXKfXHwE=" + }, + "mime-types": { + "version": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz", + "integrity": "sha1-Cdejk/A+mVp5+K+Fe3Cp4KsWVXo=", + "requires": { + "mime-db": "https://registry.npmjs.org/mime-db/-/mime-db-1.30.0.tgz" + } + }, + "morgan": { + "version": "https://registry.npmjs.org/morgan/-/morgan-1.9.0.tgz", + "integrity": "sha1-0B+mxlhZt2/PMbPLU6OCGjEdgFE=", + "requires": { + "basic-auth": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.0.tgz", + "debug": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "depd": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", + "on-finished": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "on-headers": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.1.tgz" + } + }, + "ms": { + "version": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "negotiator": { + "version": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", + "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" + }, + "neo-async": { + "version": "https://registry.npmjs.org/neo-async/-/neo-async-1.8.2.tgz", + "integrity": "sha1-MXlYiLed0ENXp8UhE6ZRg+k7ZzU=" + }, + "on-finished": { + "version": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "requires": { + "ee-first": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz" + } + }, + "on-headers": { + "version": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.1.tgz", + "integrity": "sha1-ko9dD0cNSTQmUepnlLCFfBAGk/c=" + }, + "osa": { + "version": "https://registry.npmjs.org/osa/-/osa-2.2.0.tgz", + "integrity": "sha1-faqjY42rdTJTNM5cb7XL/FnxYd8=" + }, + "osascript": { + "version": "https://registry.npmjs.org/osascript/-/osascript-1.2.0.tgz", + "integrity": "sha1-3oLikvCJHdCFisVSjlrHcgYcKkc=", + "requires": { + "duplex-child-process": "https://registry.npmjs.org/duplex-child-process/-/duplex-child-process-0.0.5.tgz" + } + }, + "parameterize": { + "version": "https://registry.npmjs.org/parameterize/-/parameterize-0.0.7.tgz", + "integrity": "sha1-X9xsBz9/cjiJI0FdA2UOHxk0VXI=" + }, + "parseurl": { + "version": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", + "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=" + }, + "path-to-regexp": { + "version": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + }, + "proxy-addr": { + "version": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.2.tgz", + "integrity": "sha1-ZXFQT0e7mI7IGAJT+F3X4UlSvew=", + "requires": { + "forwarded": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", + "ipaddr.js": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.5.2.tgz" + } + }, + "qs": { + "version": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", + "integrity": "sha1-NJzfbu+J7EXBLX1es/wMhwNDptg=" + }, + "range-parser": { + "version": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", + "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=" + }, + "raw-body": { + "version": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz", + "integrity": "sha1-vNYMd9Prk83gBQKVw/N5OJvIj4k=", + "requires": { + "bytes": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "http-errors": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", + "iconv-lite": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", + "unpipe": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz" + } + }, + "require-dir": { + "version": "https://registry.npmjs.org/require-dir/-/require-dir-0.3.2.tgz", + "integrity": "sha1-wdXHXp+//eny5rM+OD209ZS1pqk=" + }, + "safe-buffer": { + "version": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", + "integrity": "sha1-iTMSr2myEj3vcfV4iQAWce6yyFM=" + }, + "send": { + "version": "https://registry.npmjs.org/send/-/send-0.16.1.tgz", + "integrity": "sha1-pw4coh0TgsEdDZ9iMd6ygQgNerM=", + "requires": { + "debug": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "depd": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", + "destroy": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "encodeurl": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.1.tgz", + "escape-html": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "etag": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "fresh": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "http-errors": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", + "mime": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", + "ms": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "on-finished": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "range-parser": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", + "statuses": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz" + }, + "dependencies": { + "statuses": { + "version": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", + "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=" + } + } + }, + "serve-static": { + "version": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.1.tgz", + "integrity": "sha1-TFfVNASnYdjy58HooYpH2/J4pxk=", + "requires": { + "encodeurl": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.1.tgz", + "escape-html": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "parseurl": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", + "send": "https://registry.npmjs.org/send/-/send-0.16.1.tgz" + } + }, + "setprototypeof": { + "version": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", + "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=" + }, + "statuses": { + "version": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + "integrity": "sha1-u3PURtonlhBu/MG2AaJT1sRr0Ic=" + }, + "type-is": { + "version": "https://registry.npmjs.org/type-is/-/type-is-1.6.15.tgz", + "integrity": "sha1-yrEPtJCeRByChC6v4a1kbIGARBA=", + "requires": { + "media-typer": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "mime-types": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz" + } + }, + "unpipe": { + "version": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" + }, + "utils-extend": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/utils-extend/-/utils-extend-1.0.8.tgz", + "integrity": "sha1-zP17ZFQPjpDuIe7Fd2nQZRyril8=" + }, + "utils-merge": { + "version": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" + }, + "vary": { + "version": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" + } + } +} diff --git a/package.json b/package.json index 3b6edb2..af085d8 100755 --- a/package.json +++ b/package.json @@ -15,10 +15,11 @@ "url": "https://github.com/maddox/itunes-api/issues" }, "dependencies": { - "express": "^4.12.4", - "morgan": "^1.6.1", "body-parser": "^1.12.4", + "express": "^4.12.4", + "file-system": "^2.2.2", "local-itunes": "^0.3.0", + "morgan": "^1.6.1", "osa": "2.2.0", "osascript": "1.2.0", "parameterize": "^0.0.7"