From c9b4044c273733c13865c3175a70151218d09bba Mon Sep 17 00:00:00 2001 From: Henry Glendening Date: Sun, 9 Apr 2017 12:55:29 -0500 Subject: [PATCH 1/9] Add endpoint for shuffling a playlist --- app.js | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/app.js b/app.js index e7b49a4..e7d1c76 100755 --- a/app.js +++ b/app.js @@ -75,6 +75,22 @@ function playPlaylist(nameOrId){ return true; } +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 setVolume(level){ itunes = Application('iTunes'); @@ -292,7 +308,25 @@ app.put('/playlists/:id/play', function (req, res) { res.sendStatus(404) } }) +}) +app.put('/playlists/:id/shuffle', 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 (req.params.id == parameterize(playlist['name'])) { + osa(shufflePlaylist, playlist['id'], function (error, data) { + sendResponse(error, res) + }) + return + } + } + res.sendStatus(404) + } + }) }) app.get('/airplay_devices', function(req, res){ From 4061638088ad30375c4c74f77da8d60f08db24d3 Mon Sep 17 00:00:00 2001 From: Henry Glendening Date: Wed, 6 Dec 2017 01:14:50 -0600 Subject: [PATCH 2/9] Update app.js and airplay.js spacing and add missing semi-colons --- app.js | 431 +++++++++++++++++++++++++------------------------ lib/airplay.js | 8 +- 2 files changed, 223 insertions(+), 216 deletions(-) diff --git a/app.js b/app.js index e7d1c76..b30689d 100755 --- a/app.js +++ b/app.js @@ -1,30 +1,31 @@ -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 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 metadata = require('./lib/metadata'); var parameterize = require('parameterize'); -var app = express() -app.use(bodyParser.urlencoded({ extended: false })) +var 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)) +var 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,117 +40,116 @@ 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 shufflePlaylist(nameOrId){ +function shufflePlaylist(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(); } - itunes.shuffleEnabled = true - itunes.shuffleMode = "songs" + itunes.shuffleEnabled = true; + itunes.shuffleMode = 'songs'; return true; } -function setVolume(level){ +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++) { @@ -167,224 +167,231 @@ function getPlaylistsFromItunes(){ return playlistNames; } -function getPlaylists(callback){ +function getPlaylists(callback) { osa(getPlaylistsFromItunes, function (error, data) { - if (error){ - callback(error) - }else{ + if (error) { + callback(error); + } else { for (var i = 0; i < data.length; i++) { - data[i]['id'] = parameterize(data[i]['name']) + data[i]['id'] = parameterize(data[i]['name']); } - callback(null, data) + callback(null, data); } - }) + }); } -app.get('/_ping', function(req, res){ +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.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{ + if (error) { + res.sendStatus(500); + } else { for (var i = 0; i < data.length; i++) { - playlist = data[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{ + if (error) { + res.sendStatus(500); + } else { for (var i = 0; i < data.length; i++) { - playlist = data[i] + playlist = data[i]; if (req.params.id == parameterize(playlist['name'])) { osa(shufflePlaylist, playlist['id'], function (error, data) { - sendResponse(error, res) - }) - return + sendResponse(error, res); + }); + + return; } } - res.sendStatus(404) + 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', 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{ + }); +}); + +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] + 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); + } + }); +}); } }) }) diff --git a/lib/airplay.js b/lib/airplay.js index 3c16c54..3ff0ab3 100644 --- a/lib/airplay.js +++ b/lib/airplay.js @@ -1,6 +1,6 @@ module.exports = { - listAirPlayDevices: function (callback){ + listAirPlayDevices: function(callback) { itunes = Application('iTunes'); airPlayDevices = itunes.airplayDevices(); airPlayResults = []; @@ -27,7 +27,7 @@ module.exports = { return airPlayResults; }, - setSelectionStateAirPlayDevice: function (id, selected){ + setSelectionStateAirPlayDevice: function(id, selected) { itunes = Application('iTunes'); id = id.replace(/-/g, ':'); foundAirPlayDevice = null; @@ -63,7 +63,7 @@ module.exports = { return deviceData; }, - setVolumeAirPlayDevice: function (id, level){ + setVolumeAirPlayDevice: function(id, level) { itunes = Application('iTunes'); id = id.replace(/-/g, ':'); foundAirPlayDevice = null; @@ -98,4 +98,4 @@ module.exports = { return deviceData; } -} +}; From 5fca1c7e8fbd55451af02627c0f93182b6680823 Mon Sep 17 00:00:00 2001 From: Henry Glendening Date: Wed, 6 Dec 2017 01:15:40 -0600 Subject: [PATCH 3/9] Add package for file-system --- package-lock.json | 405 ++++++++++++++++++++++++++++++++++++++++++++++ package.json | 5 +- 2 files changed, 408 insertions(+), 2 deletions(-) create mode 100644 package-lock.json 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" From 4fae0829002ed754ae72eae12adc2a09709b61af Mon Sep 17 00:00:00 2001 From: Henry Glendening Date: Wed, 6 Dec 2017 01:18:26 -0600 Subject: [PATCH 4/9] Add endpoints for getting list of artists and genres. Add javascript & applescript for fetching artists & genres from iTunes and keeping cached text files of both lists. Add endpoint to manually force update of artist & genre caches --- app.js | 45 +++++++++++++++- lib/metadata.applescript | 70 ++++++++++++++++++++++++ lib/metadata.js | 112 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 225 insertions(+), 2 deletions(-) create mode 100644 lib/metadata.applescript create mode 100644 lib/metadata.js diff --git a/app.js b/app.js index b30689d..8e292ee 100755 --- a/app.js +++ b/app.js @@ -392,8 +392,49 @@ app.put('/airplay_devices/:id/volume', function (req, res) { } }); }); + +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.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.listen(process.env.PORT || 8181); 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 From 74c78356b1474a81015bd50dc882edf382447dc6 Mon Sep 17 00:00:00 2001 From: Henry Glendening Date: Wed, 13 Dec 2017 20:15:59 -0600 Subject: [PATCH 5/9] Add .DS_Store to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index eb03e3e..ac1e8f7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ node_modules *.log +.DS_Store From ea3f5e2dcf1b501b67bc92e5ce3bbf320d3544c6 Mon Sep 17 00:00:00 2001 From: Henry Glendening Date: Sat, 29 Dec 2018 02:10:05 -0600 Subject: [PATCH 6/9] Play specific genres. Use applescripts for clearing and updating Now Playing playlist due to higher efficiency than by using JXA --- app.js | 157 +++++++++++++++------- lib/playGenre.applescript | 12 ++ lib/refreshNowPlayingPlaylist.applescript | 9 ++ 3 files changed, 129 insertions(+), 49 deletions(-) create mode 100644 lib/playGenre.applescript create mode 100644 lib/refreshNowPlayingPlaylist.applescript diff --git a/app.js b/app.js index 8e292ee..9b764fb 100755 --- a/app.js +++ b/app.js @@ -1,15 +1,14 @@ -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 metadata = require('./lib/metadata'); -var parameterize = require('parameterize'); +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'); var app = express(); app.use(bodyParser.urlencoded({ extended: false })); @@ -92,6 +91,15 @@ function shufflePlaylist(nameOrId) { return true; } +function playGenre(name, callback) { + const options = { args: [genre] }; + osascript.file(path.join(__dirname, 'lib/playGenre.applescript'), options, function (err, data) { + callback(err, data); + }); + + return true; +} + function setVolume(level) { itunes = Application('iTunes'); @@ -180,52 +188,60 @@ function getPlaylists(callback) { }); } -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) { +app.put('/play', function (req, res) { iTunes.play(function (error) { sendResponse(error, res); }); }); -app.put('/pause', function(req, res) { +app.put('/pause', function (req, res) { iTunes.pause(function (error) { sendResponse(error, res); }); }); -app.put('/playpause', function(req, res) { +app.put('/playpause', function (req, res) { iTunes.playpause(function (error) { sendResponse(error, res); }); }); -app.put('/stop', function(req, res) { +app.put('/stop', function (req, res) { iTunes.stop(function (error) { sendResponse(error, res); }); }); -app.put('/previous', function(req, res) { +app.put('/previous', function (req, res) { iTunes.previous(function (error) { sendResponse(error, res); }); }); -app.put('/next', function(req, 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) { +app.put('/volume', function (req, res) { + osa(setVolume, req.body.level, function (error, data, log) { if (error) { console.log(error); res.sendStatus(500); @@ -235,8 +251,8 @@ app.put('/volume', function(req, res) { }); }); -app.put('/mute', function(req, res) { - osa(setMuted, req.body.muted, function(error, data, log) { +app.put('/mute', function (req, res) { + osa(setMuted, req.body.muted, function (error, data, log) { if (error) { console.log(error); res.sendStatus(500); @@ -246,8 +262,8 @@ app.put('/mute', function(req, res) { }); }); -app.put('/shuffle', function(req, res) { - osa(setShuffle, req.body.mode, function(error, data, log) { +app.put('/shuffle', function (req, res) { + osa(setShuffle, req.body.mode, function (error, data, log) { if (error) { console.log(error); res.sendStatus(500); @@ -257,8 +273,8 @@ app.put('/shuffle', function(req, res) { }); }); -app.put('/repeat', function(req, res) { - osa(setRepeat, req.body.mode, function(error, data, log) { +app.put('/repeat', function (req, res) { + osa(setRepeat, req.body.mode, function (error, data, log) { if (error) { console.log(error); res.sendStatus(500); @@ -268,12 +284,12 @@ app.put('/repeat', function(req, res) { }); }); -app.get('/now_playing', function(req, 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'); @@ -286,7 +302,7 @@ app.get('/playlists', function (req, res) { console.log(error); res.sendStatus(500); } else { - res.json({playlists: data}); + res.json({ playlists: data }); } }); }); @@ -332,18 +348,18 @@ app.put('/playlists/:id/shuffle', function (req, res) { }); }); -app.get('/airplay_devices', function(req, res) { - osa(airplay.listAirPlayDevices, function(error, data, log) { +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}); + res.json({ 'airplay_devices': data }); } }); }); -app.get('/airplay_devices/:id', function(req, res) { - osa(airplay.listAirPlayDevices, function(error, data, log) { +app.get('/airplay_devices/:id', function (req, res) { + osa(airplay.listAirPlayDevices, function (error, data, log) { if (error) { res.sendStatus(500); } else { @@ -361,7 +377,7 @@ app.get('/airplay_devices/:id', function(req, res) { }); app.put('/airplay_devices/:id/on', function (req, res) { - osa(airplay.setSelectionStateAirPlayDevice, req.params.id, true, function(error, data, log) { + osa(airplay.setSelectionStateAirPlayDevice, req.params.id, true, function (error, data, log) { if (error) { console.log(error); res.sendStatus(500); @@ -372,7 +388,7 @@ app.put('/airplay_devices/:id/on', function (req, res) { }); app.put('/airplay_devices/:id/off', function (req, res) { - osa(airplay.setSelectionStateAirPlayDevice, req.params.id, false, function(error, data, log) { + osa(airplay.setSelectionStateAirPlayDevice, req.params.id, false, function (error, data, log) { if (error) { console.log(error); res.sendStatus(500); @@ -383,7 +399,7 @@ app.put('/airplay_devices/:id/off', function (req, res) { }); app.put('/airplay_devices/:id/volume', function (req, res) { - osa(airplay.setVolumeAirPlayDevice, req.params.id, req.body.level, function(error, data, log) { + osa(airplay.setVolumeAirPlayDevice, req.params.id, req.body.level, function (error, data, log) { if (error) { console.log(error); res.sendStatus(500); @@ -393,19 +409,19 @@ app.put('/airplay_devices/:id/volume', function (req, res) { }); }); -app.get('/artists', function(req, res) { - metadata.getArtists(function(error, 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}); + res.json({ 'artists': data }); } }); }); -app.put('/artists/update', function(req, res) { - metadata.updateArtists(function(error, data) { +app.put('/artists/update', function (req, res) { + metadata.updateArtists(function (error, data) { if (error) { console.log(error); res.sendStatus(500); @@ -415,19 +431,46 @@ app.put('/artists/update', function(req, res) { }); }); -app.get('/genres', function(req, res){ - metadata.getGenres(function(error, data) { +app.put('/artists/:name/play', function (req, res) { + metadata.getArtists(function (error, data) { + if (error) { + res.sendStatus(500); + } else { + for (var i = 0; i < data.length; i++) { + genre = data[i]; + if (req.params.name == genre) { + refreshNowPlayingPlaylist(function (error, data) { + if (error) { + console.log(error); + res.sendStatus(500); + } else { + playGenre(genre, function (error, data) { + sendResponse(error, res); + }); + } + }); + + return; + } + } + res.sendStatus(404); + } + }); +}); + +app.get('/genres', function (req, res) { + metadata.getGenres(function (error, data) { if (error) { console.log(error); res.sendStatus(500); } else { - res.json({'genres': data}); + res.json({ 'genres': data }); } }); }); -app.put('/genres/update', function(req, res) { - metadata.updateGenres(function(error, data) { +app.put('/genres/update', function (req, res) { + metadata.updateGenres(function (error, data) { if (error) { console.log(error); res.sendStatus(500); @@ -437,4 +480,20 @@ app.put('/genres/update', function(req, res) { }); }); +app.put('/genres/:name/play', function (req, res) { + genre = req.params.name + refreshNowPlayingPlaylist(function (error, data) { + if (error) { + console.log(error); + res.sendStatus(500); + } else { + playGenre(genre, function (error, data) { + sendResponse(error, res); + }); + } + }); + + return; +}); + app.listen(process.env.PORT || 8181); diff --git a/lib/playGenre.applescript b/lib/playGenre.applescript new file mode 100644 index 0000000..950293d --- /dev/null +++ b/lib/playGenre.applescript @@ -0,0 +1,12 @@ +on run argv + if (count of argv) > 0 then + set genre_name to item 1 of argv + tell application "iTunes" + set nowPlayingPlaylist to playlist "Now Playing - API" + duplicate (shared tracks whose genre is genre_name) to nowPlayingPlaylist + 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 From b62d42a8ddab25cfc254052b867e82dc572dcba8 Mon Sep 17 00:00:00 2001 From: Henry Glendening Date: Sat, 29 Dec 2018 17:05:40 -0600 Subject: [PATCH 7/9] Refactor 'play' applescript to work with different types of filters/queries. Add route for Playing/Shuffling an artist --- app.js | 37 +++++++++++-------------------- lib/playFilteredMusic.applescript | 22 ++++++++++++++++++ lib/playGenre.applescript | 12 ---------- 3 files changed, 35 insertions(+), 36 deletions(-) create mode 100644 lib/playFilteredMusic.applescript delete mode 100644 lib/playGenre.applescript diff --git a/app.js b/app.js index 9b764fb..017e086 100755 --- a/app.js +++ b/app.js @@ -91,9 +91,9 @@ function shufflePlaylist(nameOrId) { return true; } -function playGenre(name, callback) { - const options = { args: [genre] }; - osascript.file(path.join(__dirname, 'lib/playGenre.applescript'), options, function (err, data) { +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); }); @@ -432,30 +432,19 @@ app.put('/artists/update', function (req, res) { }); app.put('/artists/:name/play', function (req, res) { - metadata.getArtists(function (error, data) { + const artist = req.params.name; + refreshNowPlayingPlaylist(function (error, data) { if (error) { + console.log(error); res.sendStatus(500); } else { - for (var i = 0; i < data.length; i++) { - genre = data[i]; - if (req.params.name == genre) { - refreshNowPlayingPlaylist(function (error, data) { - if (error) { - console.log(error); - res.sendStatus(500); - } else { - playGenre(genre, function (error, data) { - sendResponse(error, res); - }); - } - }); - - return; - } - } - res.sendStatus(404); + playFilteredMusic('artist', artist, function (error, data) { + sendResponse(error, res); + }); } }); + + return; }); app.get('/genres', function (req, res) { @@ -481,13 +470,13 @@ app.put('/genres/update', function (req, res) { }); app.put('/genres/:name/play', function (req, res) { - genre = req.params.name + const genre = req.params.name; refreshNowPlayingPlaylist(function (error, data) { if (error) { console.log(error); res.sendStatus(500); } else { - playGenre(genre, function (error, data) { + playFilteredMusic('genre', genre, function (error, data) { sendResponse(error, res); }); } 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/playGenre.applescript b/lib/playGenre.applescript deleted file mode 100644 index 950293d..0000000 --- a/lib/playGenre.applescript +++ /dev/null @@ -1,12 +0,0 @@ -on run argv - if (count of argv) > 0 then - set genre_name to item 1 of argv - tell application "iTunes" - set nowPlayingPlaylist to playlist "Now Playing - API" - duplicate (shared tracks whose genre is genre_name) to nowPlayingPlaylist - play nowPlayingPlaylist - set shuffle enabled to true - set shuffle mode to songs - end tell - end if -end run \ No newline at end of file From defe4df6364dfb310f23c6cc4f920369889d48c3 Mon Sep 17 00:00:00 2001 From: Henry Glendening Date: Tue, 1 Jan 2019 18:23:30 -0600 Subject: [PATCH 8/9] Change var's to let's in for loops to prevent mutability issues. --- app.js | 37 ++++++++++++++++++++++++++++++------- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/app.js b/app.js index 017e086..69e03e2 100755 --- a/app.js +++ b/app.js @@ -10,11 +10,11 @@ const airplay = require('./lib/airplay'); const metadata = require('./lib/metadata'); const parameterize = require('parameterize'); -var app = express(); +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'"; +const logFormat = "[:date[iso]] - :remote-addr - :method :url :status :response-time ms - :res[content-length]b'"; app.use(morgan(logFormat)); function getCurrentState() { @@ -160,7 +160,7 @@ function getPlaylistsFromItunes() { playlists = itunes.playlists(); playlistNames = []; - for (var i = 0; i < playlists.length; i++) { + for (let i = 0; i < playlists.length; i++) { playlist = playlists[i]; data = {}; @@ -180,7 +180,7 @@ function getPlaylists(callback) { if (error) { callback(error); } else { - for (var i = 0; i < data.length; i++) { + for (let i = 0; i < data.length; i++) { data[i]['id'] = parameterize(data[i]['name']); } callback(null, data); @@ -312,7 +312,7 @@ app.put('/playlists/:id/play', function (req, res) { if (error) { res.sendStatus(500); } else { - for (var i = 0; i < data.length; i++) { + 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) { @@ -333,7 +333,7 @@ app.put('/playlists/:id/shuffle', function (req, res) { if (error) { res.sendStatus(500); } else { - for (var i = 0; i < data.length; i++) { + 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) { @@ -363,7 +363,7 @@ app.get('/airplay_devices/:id', function (req, res) { if (error) { res.sendStatus(500); } else { - for (var i = 0; i < data.length; i++) { + for (let i = 0; i < data.length; i++) { device = data[i]; if (req.params.id == device['id']) { res.json(device); @@ -376,6 +376,29 @@ app.get('/airplay_devices/:id', function (req, res) { }); }); +app.get('/airplay_devices/:id/on', function (req, res) { + osa(airplay.listAirPlayDevices, function (error, data, log) { + if (error) { + console.log(error) + res.sendStatus(500); + } else { + for (let i = 0; i < data.length; i++) { + device = data[i]; + if (req.params.id == device['id']) { + if (device['selected'] == true) { + res.send((1).toString()); + } else { + res.send((0).toString()); + } + return; + } + } + + 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) { From 90e78f09269955959406bb0f1122be6adc9ed740 Mon Sep 17 00:00:00 2001 From: Henry Glendening Date: Tue, 1 Jan 2019 22:30:59 -0600 Subject: [PATCH 9/9] Add getter for iTunes volume --- app.js | 39 ++++++++++++++++----------------------- lib/airplay.js | 2 ++ 2 files changed, 18 insertions(+), 23 deletions(-) diff --git a/app.js b/app.js index 69e03e2..cb6f82d 100755 --- a/app.js +++ b/app.js @@ -100,6 +100,11 @@ function playFilteredMusic(filter_type, name, callback) { return true; } +function getVolume() { + itunes = Application('iTunes'); + return {'volume': itunes.soundVolume()}; +} + function setVolume(level) { itunes = Application('iTunes'); @@ -240,6 +245,17 @@ app.put('/next', function (req, 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) { @@ -376,29 +392,6 @@ app.get('/airplay_devices/:id', function (req, res) { }); }); -app.get('/airplay_devices/:id/on', function (req, res) { - osa(airplay.listAirPlayDevices, function (error, data, log) { - if (error) { - console.log(error) - res.sendStatus(500); - } else { - for (let i = 0; i < data.length; i++) { - device = data[i]; - if (req.params.id == device['id']) { - if (device['selected'] == true) { - res.send((1).toString()); - } else { - res.send((0).toString()); - } - return; - } - } - - 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) { diff --git a/lib/airplay.js b/lib/airplay.js index 3ff0ab3..6ad2bbc 100644 --- a/lib/airplay.js +++ b/lib/airplay.js @@ -4,6 +4,7 @@ module.exports = { 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();