From 17de5e9b92784864c037b1fd41e750a3ac6fb775 Mon Sep 17 00:00:00 2001 From: Jonathan Cremin Date: Sat, 13 Dec 2014 00:00:49 +0000 Subject: [PATCH] Update the rest of the libraries for bluebird --- lib/playmusic.js | 493 ++++++++++++++++++++++++++++++++++ lib/services/deezer/index.js | 44 ++- lib/services/google/index.js | 63 ++--- lib/services/itunes/index.js | 31 +-- lib/services/rdio/index.js | 48 ++-- lib/services/spotify/index.js | 40 +-- lib/services/youtube/index.js | 13 +- package.json | 3 +- 8 files changed, 591 insertions(+), 144 deletions(-) create mode 100644 lib/playmusic.js diff --git a/lib/playmusic.js b/lib/playmusic.js new file mode 100644 index 0000000..62785b9 --- /dev/null +++ b/lib/playmusic.js @@ -0,0 +1,493 @@ +/* Node-JS Google Play Music API +* +* Written by Jamon Terrell +* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at http://mozilla.org/MPL/2.0/. +* +* Based partially on the work of the Google Play Music resolver for Tomahawk (https://github.com/tomahawk-player/tomahawk-resolvers/blob/master/gmusic/content/contents/code/gmusic.js) +* and the gmusicapi project by Simon Weber (https://github.com/simon-weber/Unofficial-Google-Music-API/blob/develop/gmusicapi/protocol/mobileclient.py). +*/ +var https = require('https'); +var querystring = require('querystring'); +var url = require('url'); +var CryptoJS = require("crypto-js"); +var uuid = require('node-uuid'); +var util = require('util'); + +var pmUtil = {}; +pmUtil.parseKeyValues = function(body) { + var obj = {}; + body.split("\n").forEach(function(line) { + var pos = line.indexOf("="); + if(pos > 0) obj[line.substr(0, pos)] = line.substr(pos+1); + }); + return obj; +}; +pmUtil.Base64 = { + _map: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_", + stringify: CryptoJS.enc.Base64.stringify, + parse: CryptoJS.enc.Base64.parse +}; +pmUtil.salt = function(len) { + return Array.apply(0, Array(len)).map(function() { + return (function(charset){ + return charset.charAt(Math.floor(Math.random() * charset.length)); + }('abcdefghijklmnopqrstuvwxyz0123456789')); + }).join(''); +}; + + +var PlayMusic = function() {}; + +PlayMusic.prototype._baseURL = 'https://www.googleapis.com/sj/v1.5/'; +PlayMusic.prototype._webURL = 'https://play.google.com/music/'; +PlayMusic.prototype._mobileURL = 'https://android.clients.google.com/music/'; +PlayMusic.prototype._accountURL = 'https://www.google.com/accounts/'; + +PlayMusic.prototype.request = function(options) { + var opt = url.parse(options.url); + opt.headers = {}; + opt.method = options.method || "GET"; + if(typeof options.options === "object") { + Object.keys(options.options).forEach(function(k) { + opt[k] = options.options[k]; + }); + } + if(typeof this._token !== "undefined") opt.headers.Authorization = "GoogleLogin auth=" + this._token; + opt.headers["Content-type"] = options.contentType || "application/x-www-form-urlencoded"; + + var req = https.request(opt, function(res) { + res.setEncoding('utf8'); + var body = ""; + res.on('data', function(chunk) { + body += chunk; + }); + res.on('end', function() { + if(res.statusCode === 200) { + options.success(body, res); + } else { + options.error(body, null, res); + } + }); + res.on('error', function() { + options.error(null, Array.prototype.slice.apply(arguments), res); + }); + }); + if(typeof options.data !== "undefined") req.write(options.data); + req.end(); +}; + + +PlayMusic.prototype.init = function(config, next) { + var that = this; + + this._email = config.email; + this._password = config.password; + + // load signing key + var s1 = CryptoJS.enc.Base64.parse('VzeC4H4h+T2f0VI180nVX8x+Mb5HiTtGnKgH52Otj8ZCGDz9jRWyHb6QXK0JskSiOgzQfwTY5xgLLSdUSreaLVMsVVWfxfa8Rw=='); + var s2 = CryptoJS.enc.Base64.parse('ZAPnhUkYwQ6y5DdQxWThbvhJHN8msQ1rqJw0ggKdufQjelrKuiGGJI30aswkgCWTDyHkTGK9ynlqTkJ5L4CiGGUabGeo8M6JTQ=='); + + for(var idx = 0; idx < s1.words.length; idx++) { + s1.words[idx] ^= s2.words[idx]; + } + + this._key = s1; + + this._login(function(err, response) { + that._token = response.Auth; + that._getXt(function(err, xt) { + if (err) { + return next(err); + } + that._xt = xt; + that.getSettings(function(err, response) { + if (err) { + return next(err); + } + that._allAccess = response.settings.isSubscription; + + var devices = response.settings.devices.filter(function(d) { + return d.type === "PHONE" || d.type === "IOS"; + }); + + if(devices.length > 0) { + that._deviceId = devices[0].id.slice(2); + next(null, response); + } else { + next(new Error("Unable to find a usable device on your account, access from a mobile device and try again")); + } + }); + }); + }); +}; + +PlayMusic.prototype._login = function (next) { + var that = this; + var data = { + accountType: "HOSTED_OR_GOOGLE", + Email: that._email.trim(), + Passwd: that._password.trim(), + service: "sj", + source: "node-gmusic" + }; + this.request({ + method: "POST", + url: this._accountURL + "ClientLogin", + contentType: "application/x-www-form-urlencoded", + data: querystring.stringify(data), // @TODO make this.request auto serialize based on contentType + success: function(data, res) { + var obj = pmUtil.parseKeyValues(data); + next(null, obj); + }, + error: function(data, err, res) { + next(new Error("login failed!")); + } + }); +}; + +PlayMusic.prototype._getXt = function (next) { + var that = this; + this.request({ + method: "HEAD", + url: this._webURL + "listen", + success: function(data, res) { + // @TODO replace with real cookie handling + var cookies = {}; + res.headers['set-cookie'].forEach(function(c) { + var pos = c.indexOf("="); + if(pos > 0) cookies[c.substr(0, pos)] = c.substr(pos+1, c.indexOf(";")-(pos+1)); + }); + + if (typeof cookies.xt !== "undefined") { + next(null, cookies.xt); + } else { + next(new Error("xt cookie missing")); + } + }, + error: function(data, err, res) { + next(new Error("request for xt cookie failed")); + } + }); +}; + +/** +* Returns settings / device ids authorized for account. +* +* @param success function(settings) - success callback +* @param error function(data, err, res) - error callback +*/ +PlayMusic.prototype.getSettings = function(next) { + var that = this; + + this.request({ + method: "POST", + url: this._webURL + "services/loadsettings?" + querystring.stringify({u: 0, xt: this._xt}), + contentType: "application/json", + data: JSON.stringify({"sessionId": ""}), + success: function(body, res) { + var response = JSON.parse(body); + next(null, response); + }, + error: function(body, err, res) { + next(new Error("error loading settings")); + } + }); +}; + +/** +* Returns list of all tracks +* +* @param success function(trackList) - success callback +* @param error function(data, err, res) - error callback +*/ +PlayMusic.prototype.getLibrary = PlayMusic.prototype.getAllTracks = function(next) { + var that = this; + this.request({ + method: "POST", + url: this._baseURL + "trackfeed", + success: function(data, res) { + next(null, JSON.parse(data)); + }, + error: function(data, err, res) { + next(new Error("error retrieving all tracks")); + } + }); +}; + +/** +* Returns stream URL for a track. +* +* @param id string - track id, hyphenated is preferred, but "nid" will work for all access tracks (not uploaded ones) +* @param success function(streamUrl) - success callback +* @param error function(data, err, res) - error callback +*/ +PlayMusic.prototype.getStreamUrl = function (id, next) { + var that = this; + var salt = pmUtil.salt(13); + var sig = CryptoJS.HmacSHA1(id + salt, this._key).toString(pmUtil.Base64); + var qp = { + u: "0", + net: "wifi", + pt: "e", + targetkbps: "8310", + slt: salt, + sig: sig + }; + if(id.charAt(0) === "T") { + qp.mjck = id; + } else { + qp.songid = id; + } + + var qstring = querystring.stringify(qp); + this.request({ + method: "GET", + url: this._mobileURL + 'mplay?' + qstring, + options: { headers: { "X-Device-ID": this._deviceId } }, + success: function(data, res) { + next(new Error("successfully retrieved stream urls, but wasn't expecting that...")); + }, + error: function(data, err, res) { + if(res.statusCode === 302) { + next(null, res.headers.location); + } else { + next(new Error("error getting stream urls")); + } + } + }); +}; + +/** +* Searches for All Access tracks. +* +* @param text string - search text +* @param maxResults int - max number of results to return +* @param success function(searchResults) - success callback +* @param error function(data, err, res) - error callback +*/ +PlayMusic.prototype.search = function (text, maxResults, next) { + var that = this; + var qp = { + q: text, + "max-results": maxResults + }; + var qstring = querystring.stringify(qp); + this.request({ + method: "GET", + url: this._baseURL + 'query?' + qstring, + success: function(data, res) { + next(null, JSON.parse(data)); + }, + error: function(data, err, res) { + next(new Error("error getting search results")); + } + }); +}; + +/** +* Returns list of all playlists. +* +* @param success function(playlists) - success callback +* @param error function(data, err, res) - error callback +*/ +PlayMusic.prototype.getPlayLists = function (next) { + var that = this; + this.request({ + method: "POST", + url: this._baseURL + 'playlistfeed', + success: function(data, res) { + next(null, JSON.parse(data)); + }, + error: function(data, err, res) { + next(new Error("error getting playlist results")); + } + }); +}; + +/** +* Creates a new playlist +* +* @param playlistName string - the playlist name +* @param success function(mutationStatus) - success callback +* @param error function(data, err, res) - error callback +*/ +PlayMusic.prototype.addPlayList = function (playlistName, next) { + var that = this; + var mutations = [ + { + "create": { + "creationTimestamp": -1, + "deleted": false, + "lastModifiedTimestamp": 0, + "name": playlistName, + "type": "USER_GENERATED" + } + } + ]; + this.request({ + method: "POST", + contentType: "application/json", + url: this._baseURL + 'playlistbatch?' + querystring.stringify({alt: "json"}), + data: JSON.stringify({"mutations": mutations}), + success: function(data, res) { + next(null, JSON.parse(data)); + }, + error: function(data, err, res) { + next(new Error("error adding a playlist")); + } + }); +}; + +/** +* Adds a track to end of a playlist. +* +* @param songId int - the song id +* @param playlistId int - the playlist id +* @param success function(mutationStatus) - success callback +* @param error function(data, err, res) - error callback +*/ +PlayMusic.prototype.addTrackToPlayList = function (songId, playlistId, next) { + var that = this; + var mutations = [ + { + "create": { + "clientId": uuid.v1(), + "creationTimestamp": "-1", + "deleted": "false", + "lastModifiedTimestamp": "0", + "playlistId": playlistId, + "source": (songId.indexOf("T") == 0 ? "2" : "1"), + "trackId": songId + } + } + ]; + this.request({ + method: "POST", + contentType: "application/json", + url: this._baseURL + 'plentriesbatch?' + querystring.stringify({alt: "json"}), + data: JSON.stringify({"mutations": mutations}), + success: function(data, res) { + next(null, JSON.parse(data)); + }, + error: function(data, err, res) { + next(new Error("error adding a track into a playlist")); + } + }); +}; + +/** +* Removes given entry id from playlist entries +* +* @param entryId int - the entry id. You can get this from getPlayListEntries +* @param success function(mutationStatus) - success callback +* @param error function(data, err, res) - error callback +*/ +PlayMusic.prototype.removePlayListEntry = function (entryId, next) { + var that = this; + var mutations = [ { "delete": entryId } ]; + + this.request({ + method: "POST", + contentType: "application/json", + url: this._baseURL + 'plentriesbatch?' + querystring.stringify({alt: "json"}), + data: JSON.stringify({"mutations": mutations}), + success: function(data, res) { + next(null, JSON.parse(data)); + }, + error: function(data, err, res) { + next(new Error("error removing a playlist entry")); + } + }); +}; + +/** +* Returns tracks on all playlists. +* +* @param success function(playlistEntries) - success callback +* @param error function(data, err, res) - error callback +*/ +PlayMusic.prototype.getPlayListEntries = function (next) { + var that = this; + this.request({ + method: "POST", + url: this._baseURL + 'plentryfeed', + success: function(data, res) { + next(null, JSON.parse(data)); + }, + error: function(data, err, res) { + next(new Error("error getting playlist results")); + } + }); +}; + +/** +* Returns info about an All Access album. Does not work for uploaded songs. +* +* @param albumId string All Access album "nid" -- WILL NOT ACCEPT album "id" (requires "T" id, not hyphenated id) +* @param includeTracks boolean -- include track list +* @param success function(albumList) - success callback +* @param error function(data, err, res) - error callback +*/ +PlayMusic.prototype.getAlbum = function (albumId, includeTracks, next) { + var that = this; + this.request({ + method: "GET", + url: this._baseURL + "fetchalbum?" + querystring.stringify({nid: albumId, "include-tracks": includeTracks, alt: "json"}), + success: function(data, res) { + next(null, JSON.parse(data)); + }, + error: function(data, err, res) { + next(new Error("error getting album tracks")); + } + }); +}; + +/** +* Returns info about an All Access track. Does not work for uploaded songs. +* +* @param trackId string All Access track "nid" -- WILL NOT ACCEPT track "id" (requires "T" id, not hyphenated id) +* @param success function(trackInfo) - success callback +* @param error function(data, err, res) - error callback +*/ +PlayMusic.prototype.getTrack = function (trackId, next) { + var that = this; + this.request({ + method: "GET", + url: this._baseURL + "fetchtrack?" + querystring.stringify({nid: trackId, alt: "json"}), + success: function(data, res) { + next(null, JSON.parse(data)); + }, + error: function(data, err, res) { + next(new Error("error getting album tracks")); + } + }); +}; + +/** +* Returns Artist Info, top tracks, albums, related artists +* +* @param artistId string - not sure which id this is +* @param includeAlbums boolean - should album list be included in result +* @param topTrackCount int - number of top tracks to return +* @param relatedArtistCount int - number of related artists to return +* @param success function(artistInfo) - success callback +* @param error function(data, err, res) - error callback +*/ +PlayMusic.prototype.getArtist = function (artistId, includeAlbums, topTrackCount, relatedArtistCount, success, error) { + var that = this; + this.request({ + method: "GET", + url: this._baseURL + "fetchartist?" + querystring.stringify({nid: artistId, "include-albums": includeAlbums, "num-top-tracks": topTrackCount, "num-related-artists": relatedArtistCount, alt: "json"}), + success: function(data, res) { + next(null, JSON.parse(data)); + }, + error: function(data, err, res) { + next(new Error("error getting album tracks")); + } + }); +}; + +module.exports = exports = PlayMusic; diff --git a/lib/services/deezer/index.js b/lib/services/deezer/index.js index 7d85506..4969e69 100644 --- a/lib/services/deezer/index.js +++ b/lib/services/deezer/index.js @@ -1,7 +1,8 @@ "use strict"; var parse = require('url').parse; +var Promise = require('bluebird'); var request = require('superagent'); -var Q = require('q'); +require('superagent-bluebird-promise'); module.exports.id = "deezer"; @@ -9,35 +10,31 @@ var apiRoot = "https://api.deezer.com"; module.exports.match = require('./url').match; -module.exports.parseUrl = function(url, next) { - var deferred = Q.defer(); +module.exports.parseUrl = function(url) { var matches = parse(url).path.match(/\/(album|track)[\/]+([^\/]+)/); if (matches && matches[2]) { - module.exports.lookupId(matches[2], matches[1]).then(deferred.resolve); + return module.exports.lookupId(matches[2], matches[1]); } else { - deferred.reject(); + throw new Error(); } - return deferred.promise; } module.exports.lookupId = function(id, type) { - var deferred = Q.defer(); var path = "/" + type + "/" + id; - request.get(apiRoot + path, function(res) { + return request.get(apiRoot + path).promise().then(function(res) { var result = res.body; if (res.body.error) { var error = new Error("Not Found"); error.status = 404; - deferred.reject(error); - return; + throw error; } var cover = result.cover || result.album.cover; - request.get(cover).redirects(0).end(function(res) { + return request.get(cover).redirects(0).promise().then(function(res) { var artwork = res.headers.location.replace("120x120", "200x200"); if (type == "album") { - deferred.resolve({ + return { service: "deezer", type: type, id: result.id, @@ -48,9 +45,9 @@ module.exports.lookupId = function(id, type) { artist: { name: result.artist.name }, - }); + }; } else if (type == "track") { - deferred.resolve({ + return { service: "deezer", type: type, id: result.id, @@ -64,15 +61,15 @@ module.exports.lookupId = function(id, type) { album: { name: result.album.title } - }); - }; + }; + } else { + throw new Error(); + } }); }); - return deferred.promise; }; -module.exports.search = function(data, next) { - var deferred = Q.defer(); +module.exports.search = function(data) { var query, album; var type = data.type; @@ -85,7 +82,7 @@ module.exports.search = function(data, next) { } var path = "/search/" + type + "?q=" + encodeURIComponent(query); - request.get(apiRoot + path, function(res) { + return request.get(apiRoot + path).promise().then(function(res) { if (!res.body.data[0]) { var matches = album.match(/^[^\(\[]+/); if (matches[0] && matches[0] != album) { @@ -95,13 +92,12 @@ module.exports.search = function(data, next) { } else if (type == "track") { cleanedData.album.name = matches[0].trim(); } - module.exports.search(cleanedData).then(deferred.resolve); + return module.exports.search(cleanedData); } else { - deferred.resolve({service: "deezer"}); + return {service: "deezer"}; } } else { - module.exports.lookupId(res.body.data[0].id, type).then(deferred.resolve); + return module.exports.lookupId(res.body.data[0].id, type); } }); - return deferred.promise; }; diff --git a/lib/services/google/index.js b/lib/services/google/index.js index a906bf9..6992932 100644 --- a/lib/services/google/index.js +++ b/lib/services/google/index.js @@ -1,27 +1,22 @@ "use strict"; var parse = require("url").parse; -var PlayMusic = require('playmusic'); -var pm = new PlayMusic(); -var Q = require('q'); +var Promise = require('bluebird'); +var PlayMusic = require('../../playmusic'); +var pm = Promise.promisifyAll(new PlayMusic()); module.exports.id = "google"; if (!process.env.GOOGLE_EMAIL || !process.env.GOOGLE_PASSWORD) { - console.warn("GOOGLE_EMAIL or GOOGLE_PASSWORD environment variables not found, deactivating Rdio."); + console.warn("GOOGLE_EMAIL or GOOGLE_PASSWORD environment variables not found, deactivating Google Play Music."); return; } -var ready = Q.defer(); - -pm.init({email: process.env.GOOGLE_EMAIL, password: process.env.GOOGLE_PASSWORD}, function() { - ready.resolve(); -}); +var ready = pm.initAsync({email: process.env.GOOGLE_EMAIL, password: process.env.GOOGLE_PASSWORD}); module.exports.match = require('./url').match; module.exports.parseUrl = function(url) { - var deferred = Q.defer(); - ready.promise.then(function() { + return ready.then(function() { var parsed = parse(url.replace(/\+/g, "%20")); var path = parsed.path; var hash = parsed.hash; @@ -33,25 +28,23 @@ module.exports.parseUrl = function(url) { var album = decodeURIComponent(parts[4]); if (id.length > 0) { - deferred.resolve({id: id, type: type}); + return {id: id, type: type}; } else { - module.exports.search({type: type, name:album, artist: {name: artist}}).then(deferred.resolve); + return module.exports.search({type: type, name:album, artist: {name: artist}}); } } else if(path) { var matches = path.match(/\/music\/m\/([\w]+)/); var type = matches[1][0] == "T" ? "track" : "album"; - module.exports.lookupId(matches[1], type).then(deferred.resolve); + return module.exports.lookupId(matches[1], type); } - }); - return deferred.promise; + }) } -module.exports.lookupId = function(id, type, next) { - var deferred = Q.defer(); - ready.promise.then(function() { +module.exports.lookupId = function(id, type) { + return ready.then(function() { if (type == "album") { - pm.getAlbum(id, false, function(album) { - deferred.resolve({ + return pm.getAlbumAsync(id, false).then(function(album) { + return { service: "google", type: "album", id: album.albumId, @@ -62,13 +55,13 @@ module.exports.lookupId = function(id, type, next) { artist: { name: album.artist } - }); + }; }, function(error) { - deferred.reject(error); + throw error; }); } else if (type == "track") { - pm.getAllAccessTrack(id, function(track) { - deferred.resolve({ + return pm.getTrackAsync(id).then(function(track) { + return { service: "google", type: "track", id: track.nid, @@ -82,18 +75,16 @@ module.exports.lookupId = function(id, type, next) { artist: { name: track.artist } - }); + }; }, function(error) { - deferred.reject(error); + throw error; }); } }); - return deferred.promise; } module.exports.search = function(data) { - var deferred = Q.defer(); - ready.promise.then(function() { + return ready.then(function() { var query, album; var type = data.type; @@ -105,7 +96,7 @@ module.exports.search = function(data) { album = data.album.name; } - pm.search(query, 5, function(result) { + return pm.searchAsync(query, 5).then(function(result) { if (!result.entries) { var matches = album.match(/^[^\(\[]+/); if (matches[0] && matches[0] != album) { @@ -115,11 +106,10 @@ module.exports.search = function(data) { } else if (type == "track") { cleanedData.album.name = matches[0].trim(); } - module.exports.search(cleanedData).then(deferred.resolve); + return module.exports.search(cleanedData); } else { - deferred.resolve({service: "googleplaymusic"}); + return {service: "googleplaymusic"}; } - return; } var result = result.entries.filter(function(result) { return result[type]; @@ -128,7 +118,7 @@ module.exports.search = function(data) { }).shift(); if (!result) { - deferred.resolve({service: "google"}); + return {service: "google"}; } else { var id; if (type == "album") { @@ -137,9 +127,8 @@ module.exports.search = function(data) { id = result.track.nid; } - module.exports.lookupId(id, type).then(deferred.resolve); + return module.exports.lookupId(id, type); } }); }); - return deferred.promise; } diff --git a/lib/services/itunes/index.js b/lib/services/itunes/index.js index 8f91104..9605cd4 100644 --- a/lib/services/itunes/index.js +++ b/lib/services/itunes/index.js @@ -1,8 +1,9 @@ "use strict"; -var parse = require('url').parse; +var parse = require("url").parse; +var Promise = require('bluebird'); var querystring = require('querystring'); var request = require('superagent'); -var Q = require('q'); +require('superagent-bluebird-promise'); module.exports.id = "itunes"; @@ -11,7 +12,6 @@ var apiRoot = "https://itunes.apple.com"; module.exports.match = require('./url').match; module.exports.parseUrl = function(url) { - var deferred = Q.defer(); var parsed = parse(url); var matches = parsed.path.match(/[\/]?([\/]?[a-z]{2}?)?[\/]+album[\/]+([^\/]+)[\/]+([^\?]+)/); var query = querystring.parse(parsed.query); @@ -23,16 +23,13 @@ module.exports.parseUrl = function(url) { type = "track"; id = query.i; } - module.exports.lookupId(id, type, matches[1] || "us").then(deferred.resolve, deferred.reject); + return module.exports.lookupId(id, type, matches[1] || "us"); } else { - deferred.reject(); + throw new Error(); } - return deferred.promise; }; module.exports.lookupId = function(id, type, cc) { - var deferred = Q.defer(); - if (id.match(/^[a-z]{2}/)) { cc = id.substr(0,2); id = id.substr(2); @@ -43,13 +40,13 @@ module.exports.lookupId = function(id, type, cc) { path = "/" + cc + path; } - request.get(apiRoot + path, function(res) { + return request.get(apiRoot + path).promise().then(function(res) { var data = JSON.parse(res.text); if (!data.results || data.resultCount == 0 || !data.results[0].collectionId) { var error = new Error("Not Found"); error.status = 404; - deferred.reject(error); + throw error; } else { var result = data.results[0]; @@ -72,14 +69,12 @@ module.exports.lookupId = function(id, type, cc) { }; } - deferred.resolve(item); + return item; } }); - return deferred.promise; }; module.exports.search = function(data) { - var deferred = Q.defer(); var query, album, entity; var type = data.type; @@ -94,7 +89,7 @@ module.exports.search = function(data) { } var path = "/search?term=" + encodeURIComponent(query) + "&media=music&entity=" + entity; - request.get(apiRoot + path, function(res) { + return request.get(apiRoot + path).promise().then(function(res) { var result = JSON.parse(res.text); if (!result.results[0]) { @@ -106,9 +101,9 @@ module.exports.search = function(data) { } else if (type == "track") { cleanedData.album.name = matches[0].trim(); } - module.exports.search(cleanedData).then(deferred.resolve); + return module.exports.search(cleanedData); } else { - deferred.resolve({service: "itunes"}); + return {service: "itunes"}; } } else { var result = result.results[0]; @@ -131,9 +126,7 @@ module.exports.search = function(data) { name: result.collectionName }; } - deferred.resolve(item); + return item; } }); - - return deferred.promise; }; diff --git a/lib/services/rdio/index.js b/lib/services/rdio/index.js index a99eed6..9fc4f81 100644 --- a/lib/services/rdio/index.js +++ b/lib/services/rdio/index.js @@ -1,6 +1,6 @@ "use strict"; var parse = require('url').parse; -var Q = require('q'); +var Promise = require('bluebird'); module.exports.id = "rdio"; @@ -14,21 +14,18 @@ var rdio = require('rdio')({ rdio_api_shared: process.env.RDIO_API_SHARED, }); +var rdio = Promise.promisifyAll(rdio); + module.exports.match = require('./url').match; module.exports.lookupId = function(id) { - var deferred = Q.defer(); - rdio.api("", "", { - method: 'getObjectFromShortCode', - short_code: id, - }, function(err, results) { - if (err || !JSON.parse(results).result) { + return rdio.apiAsync("", "", {method: 'getObjectFromShortCode', short_code: id}).then(function(results) { + if (!JSON.parse(results[0]).result) { var error = new Error("Not Found"); error.status = 404; - deferred.reject(error); - return; + throw error; } - var result = JSON.parse(results).result; + var result = JSON.parse(results[0]).result; var parsed = parse(result.shortUrl) var id = parsed.path.replace("/x/", "").replace("/", ""); var type = result.album ? "track" : "album"; @@ -49,13 +46,11 @@ module.exports.lookupId = function(id) { name: result.album }; } - deferred.resolve(item); + return item; }); - return deferred.promise; }; module.exports.parseUrl = function(url) { - var deferred = Q.defer(); var parsed = parse(url); var data; @@ -73,16 +68,16 @@ module.exports.parseUrl = function(url) { } else { var error = new Error("Not Found"); error.status = 404; - return deferred.reject(error); + throw error; } - rdio.api("", "", data, function(err, results) { - var results = JSON.parse(results); + return rdio.apiAsync("", "", data).then(function(results) { + var results = JSON.parse(results[0]); var result = results.result; if (!result || results.status != "ok") { var error = new Error("Not Found"); error.status = 404; - return deferred.reject(error); + throw error; } else { var parsed = parse(result.shortUrl) var id = parsed.path.replace("/x/", "").replace("/", ""); @@ -104,14 +99,12 @@ module.exports.parseUrl = function(url) { name: result.album }; } - deferred.resolve(item); + return item; } }); - return deferred.promise; }; module.exports.search = function(data) { - var deferred = Q.defer(); var query, albumClean; var type = data.type; @@ -123,12 +116,8 @@ module.exports.search = function(data) { albumClean = data.album.name.match(/([^\(\[]+)/)[0]; } - rdio.api("", "", { - query: query, - method: 'search', - types: type, - }, function(err, results) { - var results = JSON.parse(results).result.results; + return rdio.apiAsync("", "", {query: query, method: 'search', types: type}).then(function(results) { + var results = JSON.parse(results[0]).result.results; var result = results.filter(function(result) { if (type == "album" && result.name.match(/([^\(\[]+)/)[0] == albumClean) { @@ -147,9 +136,9 @@ module.exports.search = function(data) { } else if (type == "track") { cleanedData.album.name = matches[0].trim(); } - module.exports.search(cleanedData).then(deferred.resolve); + return module.exports.search(cleanedData); } else { - deferred.resolve({service: "rdio"}); + return {service: "rdio"}; } } else { var parsed = parse(result.shortUrl) @@ -171,8 +160,7 @@ module.exports.search = function(data) { name: result.album }; } - deferred.resolve(item); + return item; } }); - return deferred.promise; }; diff --git a/lib/services/spotify/index.js b/lib/services/spotify/index.js index f253f62..fa4a0d5 100644 --- a/lib/services/spotify/index.js +++ b/lib/services/spotify/index.js @@ -1,36 +1,32 @@ "use strict"; var parse = require('url').parse; -var spotify = require('spotify'); -var Q = require('q'); +var Promise = require('bluebird'); +var spotify = Promise.promisifyAll(require('spotify')); module.exports.id = "spotify"; module.exports.match = require('./url').match; module.exports.parseUrl = function(url) { - var deferred = Q.defer(); var matches = parse(url).path.match(/\/(album|track)[\/]+([^\/]+)/); if (matches && matches[2]) { - module.exports.lookupId(matches[2], matches[1]).then(deferred.resolve); + return module.exports.lookupId(matches[2], matches[1]); } - return deferred.promise; } module.exports.lookupId = function(id, type) { - var deferred = Q.defer(); - spotify.lookup({id: id, type: type}, function(err, data) { - if ( err || data.error) { + return spotify.lookupAsync({id: id, type: type}).then(function(data) { + if (data.error) { var error = new Error("Not Found"); error.status = 404; - deferred.reject(error); - return; + throw error; } var artist = data.artists[0]; if (type == "album") { - deferred.resolve({ + return { service: "spotify", type: type, id: data.id, @@ -41,9 +37,9 @@ module.exports.lookupId = function(id, type) { artist: { name: artist.name } - }); + }; } else if (type == "track") { - deferred.resolve({ + return { service: "spotify", type: type, id: data.id, @@ -57,14 +53,12 @@ module.exports.lookupId = function(id, type) { album: { name: data.album.name } - }) + }; } }); - return deferred.promise; } module.exports.search = function(data) { - var deferred = Q.defer(); var query, album; var type = data.type; @@ -76,12 +70,7 @@ module.exports.search = function(data) { album = data.album.name; } - spotify.search({query: query, type: type}, function(err, results) { - if ( err ) { - deferred.resolve({service: "spotify"}); - return; - } - + return spotify.searchAsync({query: query, type: type}).then(function(results) { if (!results[type + "s"].items[0]) { var matches = album.match(/^[^\(\[]+/); if (matches[0] && matches[0] != album) { @@ -91,14 +80,13 @@ module.exports.search = function(data) { } else if (type == "track") { cleanedData.album.name = matches[0].trim(); } - module.exports.search(cleanedData).then(deferred.resolve); + return module.exports.search(cleanedData); } else { - deferred.resolve({service: "spotify"}); + return {service: "spotify"}; } } else { - module.exports.lookupId(results[type + "s"].items[0].id, type).then(deferred.resolve); + return module.exports.lookupId(results[type + "s"].items[0].id, type); } }); - return deferred.promise; } diff --git a/lib/services/youtube/index.js b/lib/services/youtube/index.js index 76a40f5..e99f256 100644 --- a/lib/services/youtube/index.js +++ b/lib/services/youtube/index.js @@ -1,7 +1,8 @@ "use strict"; var parse = require('url').parse; +var Promise = require('bluebird'); var request = require('superagent'); -var Q = require('q'); +require('superagent-bluebird-promise'); module.exports.id = "youtube"; @@ -19,7 +20,6 @@ var apiRoot = "https://www.googleapis.com/youtube/v3"; module.exports.match = require('./url').match; module.exports.search = function(data) { - var deferred = Q.defer(); var query, album; var type = data.type; @@ -33,13 +33,13 @@ module.exports.search = function(data) { var path = "/search?part=snippet&q=" + encodeURIComponent(query) + "&type=video&videoCaption=any&videoCategoryId=10&key=" + credentials.key; - request.get(apiRoot + path, function(res) { + return request.get(apiRoot + path).promise().then(function(res) { var result = res.body.items[0]; if (!result) { - deferred.resolve({service:"youtube", type: "video"}); + return {service:"youtube", type: "video"}; } else { - deferred.resolve({ + return { service: "youtube", type: "video", id: result.id.videoId, @@ -47,8 +47,7 @@ module.exports.search = function(data) { streamUrl: "https://www.youtube.com/watch?v=" + result.id.videoId, purchaseUrl: null, artwork: result.snippet.thumbnails.medium.url, - }); + }; } }); - return deferred.promise; }; diff --git a/package.json b/package.json index cf3db9d..49b8fd9 100644 --- a/package.json +++ b/package.json @@ -14,13 +14,14 @@ "body-parser": "~1.8.1", "connect-flash": "^0.1.1", "cookie-parser": "~1.3.3", + "crypto-js": "^3.1.2-5", "debug": "~2.0.0", "ejs": "~0.8.5", "express": "~4.9.0", "express-session": "^1.9.2", "helmet": "^0.5.2", "morgan": "~1.3.0", - "playmusic": "^1.1.0", + "node-uuid": "^1.4.2", "promised-mongo": "^0.11.1", "q": "^1.1.2", "rdio": "^1.5.2",