Update the rest of the libraries for bluebird

This commit is contained in:
Jonathan Cremin 2014-12-13 00:00:49 +00:00
parent 90c1385fb3
commit 17de5e9b92
8 changed files with 591 additions and 144 deletions

493
lib/playmusic.js Normal file
View file

@ -0,0 +1,493 @@
/* Node-JS Google Play Music API
*
* Written by Jamon Terrell <git@jamonterrell.com>
*
* 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;

View file

@ -1,7 +1,8 @@
"use strict"; "use strict";
var parse = require('url').parse; var parse = require('url').parse;
var Promise = require('bluebird');
var request = require('superagent'); var request = require('superagent');
var Q = require('q'); require('superagent-bluebird-promise');
module.exports.id = "deezer"; module.exports.id = "deezer";
@ -9,35 +10,31 @@ var apiRoot = "https://api.deezer.com";
module.exports.match = require('./url').match; module.exports.match = require('./url').match;
module.exports.parseUrl = function(url, next) { module.exports.parseUrl = function(url) {
var deferred = Q.defer();
var matches = parse(url).path.match(/\/(album|track)[\/]+([^\/]+)/); var matches = parse(url).path.match(/\/(album|track)[\/]+([^\/]+)/);
if (matches && matches[2]) { if (matches && matches[2]) {
module.exports.lookupId(matches[2], matches[1]).then(deferred.resolve); return module.exports.lookupId(matches[2], matches[1]);
} else { } else {
deferred.reject(); throw new Error();
} }
return deferred.promise;
} }
module.exports.lookupId = function(id, type) { module.exports.lookupId = function(id, type) {
var deferred = Q.defer();
var path = "/" + type + "/" + id; var path = "/" + type + "/" + id;
request.get(apiRoot + path, function(res) { return request.get(apiRoot + path).promise().then(function(res) {
var result = res.body; var result = res.body;
if (res.body.error) { if (res.body.error) {
var error = new Error("Not Found"); var error = new Error("Not Found");
error.status = 404; error.status = 404;
deferred.reject(error); throw error;
return;
} }
var cover = result.cover || result.album.cover; 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"); var artwork = res.headers.location.replace("120x120", "200x200");
if (type == "album") { if (type == "album") {
deferred.resolve({ return {
service: "deezer", service: "deezer",
type: type, type: type,
id: result.id, id: result.id,
@ -48,9 +45,9 @@ module.exports.lookupId = function(id, type) {
artist: { artist: {
name: result.artist.name name: result.artist.name
}, },
}); };
} else if (type == "track") { } else if (type == "track") {
deferred.resolve({ return {
service: "deezer", service: "deezer",
type: type, type: type,
id: result.id, id: result.id,
@ -64,15 +61,15 @@ module.exports.lookupId = function(id, type) {
album: { album: {
name: result.album.title name: result.album.title
} }
}); };
}; } else {
throw new Error();
}
}); });
}); });
return deferred.promise;
}; };
module.exports.search = function(data, next) { module.exports.search = function(data) {
var deferred = Q.defer();
var query, album; var query, album;
var type = data.type; var type = data.type;
@ -85,7 +82,7 @@ module.exports.search = function(data, next) {
} }
var path = "/search/" + type + "?q=" + encodeURIComponent(query); 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]) { if (!res.body.data[0]) {
var matches = album.match(/^[^\(\[]+/); var matches = album.match(/^[^\(\[]+/);
if (matches[0] && matches[0] != album) { if (matches[0] && matches[0] != album) {
@ -95,13 +92,12 @@ module.exports.search = function(data, next) {
} else if (type == "track") { } else if (type == "track") {
cleanedData.album.name = matches[0].trim(); cleanedData.album.name = matches[0].trim();
} }
module.exports.search(cleanedData).then(deferred.resolve); return module.exports.search(cleanedData);
} else { } else {
deferred.resolve({service: "deezer"}); return {service: "deezer"};
} }
} else { } 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;
}; };

View file

@ -1,27 +1,22 @@
"use strict"; "use strict";
var parse = require("url").parse; var parse = require("url").parse;
var PlayMusic = require('playmusic'); var Promise = require('bluebird');
var pm = new PlayMusic(); var PlayMusic = require('../../playmusic');
var Q = require('q'); var pm = Promise.promisifyAll(new PlayMusic());
module.exports.id = "google"; module.exports.id = "google";
if (!process.env.GOOGLE_EMAIL || !process.env.GOOGLE_PASSWORD) { 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; return;
} }
var ready = Q.defer(); var ready = pm.initAsync({email: process.env.GOOGLE_EMAIL, password: process.env.GOOGLE_PASSWORD});
pm.init({email: process.env.GOOGLE_EMAIL, password: process.env.GOOGLE_PASSWORD}, function() {
ready.resolve();
});
module.exports.match = require('./url').match; module.exports.match = require('./url').match;
module.exports.parseUrl = function(url) { module.exports.parseUrl = function(url) {
var deferred = Q.defer(); return ready.then(function() {
ready.promise.then(function() {
var parsed = parse(url.replace(/\+/g, "%20")); var parsed = parse(url.replace(/\+/g, "%20"));
var path = parsed.path; var path = parsed.path;
var hash = parsed.hash; var hash = parsed.hash;
@ -33,25 +28,23 @@ module.exports.parseUrl = function(url) {
var album = decodeURIComponent(parts[4]); var album = decodeURIComponent(parts[4]);
if (id.length > 0) { if (id.length > 0) {
deferred.resolve({id: id, type: type}); return {id: id, type: type};
} else { } 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) { } else if(path) {
var matches = path.match(/\/music\/m\/([\w]+)/); var matches = path.match(/\/music\/m\/([\w]+)/);
var type = matches[1][0] == "T" ? "track" : "album"; 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) { module.exports.lookupId = function(id, type) {
var deferred = Q.defer(); return ready.then(function() {
ready.promise.then(function() {
if (type == "album") { if (type == "album") {
pm.getAlbum(id, false, function(album) { return pm.getAlbumAsync(id, false).then(function(album) {
deferred.resolve({ return {
service: "google", service: "google",
type: "album", type: "album",
id: album.albumId, id: album.albumId,
@ -62,13 +55,13 @@ module.exports.lookupId = function(id, type, next) {
artist: { artist: {
name: album.artist name: album.artist
} }
}); };
}, function(error) { }, function(error) {
deferred.reject(error); throw error;
}); });
} else if (type == "track") { } else if (type == "track") {
pm.getAllAccessTrack(id, function(track) { return pm.getTrackAsync(id).then(function(track) {
deferred.resolve({ return {
service: "google", service: "google",
type: "track", type: "track",
id: track.nid, id: track.nid,
@ -82,18 +75,16 @@ module.exports.lookupId = function(id, type, next) {
artist: { artist: {
name: track.artist name: track.artist
} }
}); };
}, function(error) { }, function(error) {
deferred.reject(error); throw error;
}); });
} }
}); });
return deferred.promise;
} }
module.exports.search = function(data) { module.exports.search = function(data) {
var deferred = Q.defer(); return ready.then(function() {
ready.promise.then(function() {
var query, album; var query, album;
var type = data.type; var type = data.type;
@ -105,7 +96,7 @@ module.exports.search = function(data) {
album = data.album.name; album = data.album.name;
} }
pm.search(query, 5, function(result) { return pm.searchAsync(query, 5).then(function(result) {
if (!result.entries) { if (!result.entries) {
var matches = album.match(/^[^\(\[]+/); var matches = album.match(/^[^\(\[]+/);
if (matches[0] && matches[0] != album) { if (matches[0] && matches[0] != album) {
@ -115,11 +106,10 @@ module.exports.search = function(data) {
} else if (type == "track") { } else if (type == "track") {
cleanedData.album.name = matches[0].trim(); cleanedData.album.name = matches[0].trim();
} }
module.exports.search(cleanedData).then(deferred.resolve); return module.exports.search(cleanedData);
} else { } else {
deferred.resolve({service: "googleplaymusic"}); return {service: "googleplaymusic"};
} }
return;
} }
var result = result.entries.filter(function(result) { var result = result.entries.filter(function(result) {
return result[type]; return result[type];
@ -128,7 +118,7 @@ module.exports.search = function(data) {
}).shift(); }).shift();
if (!result) { if (!result) {
deferred.resolve({service: "google"}); return {service: "google"};
} else { } else {
var id; var id;
if (type == "album") { if (type == "album") {
@ -137,9 +127,8 @@ module.exports.search = function(data) {
id = result.track.nid; id = result.track.nid;
} }
module.exports.lookupId(id, type).then(deferred.resolve); return module.exports.lookupId(id, type);
} }
}); });
}); });
return deferred.promise;
} }

View file

@ -1,8 +1,9 @@
"use strict"; "use strict";
var parse = require('url').parse; var parse = require("url").parse;
var Promise = require('bluebird');
var querystring = require('querystring'); var querystring = require('querystring');
var request = require('superagent'); var request = require('superagent');
var Q = require('q'); require('superagent-bluebird-promise');
module.exports.id = "itunes"; module.exports.id = "itunes";
@ -11,7 +12,6 @@ var apiRoot = "https://itunes.apple.com";
module.exports.match = require('./url').match; module.exports.match = require('./url').match;
module.exports.parseUrl = function(url) { module.exports.parseUrl = function(url) {
var deferred = Q.defer();
var parsed = parse(url); var parsed = parse(url);
var matches = parsed.path.match(/[\/]?([\/]?[a-z]{2}?)?[\/]+album[\/]+([^\/]+)[\/]+([^\?]+)/); var matches = parsed.path.match(/[\/]?([\/]?[a-z]{2}?)?[\/]+album[\/]+([^\/]+)[\/]+([^\?]+)/);
var query = querystring.parse(parsed.query); var query = querystring.parse(parsed.query);
@ -23,16 +23,13 @@ module.exports.parseUrl = function(url) {
type = "track"; type = "track";
id = query.i; 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 { } else {
deferred.reject(); throw new Error();
} }
return deferred.promise;
}; };
module.exports.lookupId = function(id, type, cc) { module.exports.lookupId = function(id, type, cc) {
var deferred = Q.defer();
if (id.match(/^[a-z]{2}/)) { if (id.match(/^[a-z]{2}/)) {
cc = id.substr(0,2); cc = id.substr(0,2);
id = id.substr(2); id = id.substr(2);
@ -43,13 +40,13 @@ module.exports.lookupId = function(id, type, cc) {
path = "/" + cc + path; path = "/" + cc + path;
} }
request.get(apiRoot + path, function(res) { return request.get(apiRoot + path).promise().then(function(res) {
var data = JSON.parse(res.text); var data = JSON.parse(res.text);
if (!data.results || data.resultCount == 0 || !data.results[0].collectionId) { if (!data.results || data.resultCount == 0 || !data.results[0].collectionId) {
var error = new Error("Not Found"); var error = new Error("Not Found");
error.status = 404; error.status = 404;
deferred.reject(error); throw error;
} else { } else {
var result = data.results[0]; 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) { module.exports.search = function(data) {
var deferred = Q.defer();
var query, album, entity; var query, album, entity;
var type = data.type; var type = data.type;
@ -94,7 +89,7 @@ module.exports.search = function(data) {
} }
var path = "/search?term=" + encodeURIComponent(query) + "&media=music&entity=" + entity; 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); var result = JSON.parse(res.text);
if (!result.results[0]) { if (!result.results[0]) {
@ -106,9 +101,9 @@ module.exports.search = function(data) {
} else if (type == "track") { } else if (type == "track") {
cleanedData.album.name = matches[0].trim(); cleanedData.album.name = matches[0].trim();
} }
module.exports.search(cleanedData).then(deferred.resolve); return module.exports.search(cleanedData);
} else { } else {
deferred.resolve({service: "itunes"}); return {service: "itunes"};
} }
} else { } else {
var result = result.results[0]; var result = result.results[0];
@ -131,9 +126,7 @@ module.exports.search = function(data) {
name: result.collectionName name: result.collectionName
}; };
} }
deferred.resolve(item); return item;
} }
}); });
return deferred.promise;
}; };

View file

@ -1,6 +1,6 @@
"use strict"; "use strict";
var parse = require('url').parse; var parse = require('url').parse;
var Q = require('q'); var Promise = require('bluebird');
module.exports.id = "rdio"; module.exports.id = "rdio";
@ -14,21 +14,18 @@ var rdio = require('rdio')({
rdio_api_shared: process.env.RDIO_API_SHARED, rdio_api_shared: process.env.RDIO_API_SHARED,
}); });
var rdio = Promise.promisifyAll(rdio);
module.exports.match = require('./url').match; module.exports.match = require('./url').match;
module.exports.lookupId = function(id) { module.exports.lookupId = function(id) {
var deferred = Q.defer(); return rdio.apiAsync("", "", {method: 'getObjectFromShortCode', short_code: id}).then(function(results) {
rdio.api("", "", { if (!JSON.parse(results[0]).result) {
method: 'getObjectFromShortCode',
short_code: id,
}, function(err, results) {
if (err || !JSON.parse(results).result) {
var error = new Error("Not Found"); var error = new Error("Not Found");
error.status = 404; error.status = 404;
deferred.reject(error); throw error;
return;
} }
var result = JSON.parse(results).result; var result = JSON.parse(results[0]).result;
var parsed = parse(result.shortUrl) var parsed = parse(result.shortUrl)
var id = parsed.path.replace("/x/", "").replace("/", ""); var id = parsed.path.replace("/x/", "").replace("/", "");
var type = result.album ? "track" : "album"; var type = result.album ? "track" : "album";
@ -49,13 +46,11 @@ module.exports.lookupId = function(id) {
name: result.album name: result.album
}; };
} }
deferred.resolve(item); return item;
}); });
return deferred.promise;
}; };
module.exports.parseUrl = function(url) { module.exports.parseUrl = function(url) {
var deferred = Q.defer();
var parsed = parse(url); var parsed = parse(url);
var data; var data;
@ -73,16 +68,16 @@ module.exports.parseUrl = function(url) {
} else { } else {
var error = new Error("Not Found"); var error = new Error("Not Found");
error.status = 404; error.status = 404;
return deferred.reject(error); throw error;
} }
rdio.api("", "", data, function(err, results) { return rdio.apiAsync("", "", data).then(function(results) {
var results = JSON.parse(results); var results = JSON.parse(results[0]);
var result = results.result; var result = results.result;
if (!result || results.status != "ok") { if (!result || results.status != "ok") {
var error = new Error("Not Found"); var error = new Error("Not Found");
error.status = 404; error.status = 404;
return deferred.reject(error); throw error;
} else { } else {
var parsed = parse(result.shortUrl) var parsed = parse(result.shortUrl)
var id = parsed.path.replace("/x/", "").replace("/", ""); var id = parsed.path.replace("/x/", "").replace("/", "");
@ -104,14 +99,12 @@ module.exports.parseUrl = function(url) {
name: result.album name: result.album
}; };
} }
deferred.resolve(item); return item;
} }
}); });
return deferred.promise;
}; };
module.exports.search = function(data) { module.exports.search = function(data) {
var deferred = Q.defer();
var query, albumClean; var query, albumClean;
var type = data.type; var type = data.type;
@ -123,12 +116,8 @@ module.exports.search = function(data) {
albumClean = data.album.name.match(/([^\(\[]+)/)[0]; albumClean = data.album.name.match(/([^\(\[]+)/)[0];
} }
rdio.api("", "", { return rdio.apiAsync("", "", {query: query, method: 'search', types: type}).then(function(results) {
query: query, var results = JSON.parse(results[0]).result.results;
method: 'search',
types: type,
}, function(err, results) {
var results = JSON.parse(results).result.results;
var result = results.filter(function(result) { var result = results.filter(function(result) {
if (type == "album" && result.name.match(/([^\(\[]+)/)[0] == albumClean) { if (type == "album" && result.name.match(/([^\(\[]+)/)[0] == albumClean) {
@ -147,9 +136,9 @@ module.exports.search = function(data) {
} else if (type == "track") { } else if (type == "track") {
cleanedData.album.name = matches[0].trim(); cleanedData.album.name = matches[0].trim();
} }
module.exports.search(cleanedData).then(deferred.resolve); return module.exports.search(cleanedData);
} else { } else {
deferred.resolve({service: "rdio"}); return {service: "rdio"};
} }
} else { } else {
var parsed = parse(result.shortUrl) var parsed = parse(result.shortUrl)
@ -171,8 +160,7 @@ module.exports.search = function(data) {
name: result.album name: result.album
}; };
} }
deferred.resolve(item); return item;
} }
}); });
return deferred.promise;
}; };

View file

@ -1,36 +1,32 @@
"use strict"; "use strict";
var parse = require('url').parse; var parse = require('url').parse;
var spotify = require('spotify'); var Promise = require('bluebird');
var Q = require('q'); var spotify = Promise.promisifyAll(require('spotify'));
module.exports.id = "spotify"; module.exports.id = "spotify";
module.exports.match = require('./url').match; module.exports.match = require('./url').match;
module.exports.parseUrl = function(url) { module.exports.parseUrl = function(url) {
var deferred = Q.defer();
var matches = parse(url).path.match(/\/(album|track)[\/]+([^\/]+)/); var matches = parse(url).path.match(/\/(album|track)[\/]+([^\/]+)/);
if (matches && matches[2]) { 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) { module.exports.lookupId = function(id, type) {
var deferred = Q.defer(); return spotify.lookupAsync({id: id, type: type}).then(function(data) {
spotify.lookup({id: id, type: type}, function(err, data) { if (data.error) {
if ( err || data.error) {
var error = new Error("Not Found"); var error = new Error("Not Found");
error.status = 404; error.status = 404;
deferred.reject(error); throw error;
return;
} }
var artist = data.artists[0]; var artist = data.artists[0];
if (type == "album") { if (type == "album") {
deferred.resolve({ return {
service: "spotify", service: "spotify",
type: type, type: type,
id: data.id, id: data.id,
@ -41,9 +37,9 @@ module.exports.lookupId = function(id, type) {
artist: { artist: {
name: artist.name name: artist.name
} }
}); };
} else if (type == "track") { } else if (type == "track") {
deferred.resolve({ return {
service: "spotify", service: "spotify",
type: type, type: type,
id: data.id, id: data.id,
@ -57,14 +53,12 @@ module.exports.lookupId = function(id, type) {
album: { album: {
name: data.album.name name: data.album.name
} }
}) };
} }
}); });
return deferred.promise;
} }
module.exports.search = function(data) { module.exports.search = function(data) {
var deferred = Q.defer();
var query, album; var query, album;
var type = data.type; var type = data.type;
@ -76,12 +70,7 @@ module.exports.search = function(data) {
album = data.album.name; album = data.album.name;
} }
spotify.search({query: query, type: type}, function(err, results) { return spotify.searchAsync({query: query, type: type}).then(function(results) {
if ( err ) {
deferred.resolve({service: "spotify"});
return;
}
if (!results[type + "s"].items[0]) { if (!results[type + "s"].items[0]) {
var matches = album.match(/^[^\(\[]+/); var matches = album.match(/^[^\(\[]+/);
if (matches[0] && matches[0] != album) { if (matches[0] && matches[0] != album) {
@ -91,14 +80,13 @@ module.exports.search = function(data) {
} else if (type == "track") { } else if (type == "track") {
cleanedData.album.name = matches[0].trim(); cleanedData.album.name = matches[0].trim();
} }
module.exports.search(cleanedData).then(deferred.resolve); return module.exports.search(cleanedData);
} else { } else {
deferred.resolve({service: "spotify"}); return {service: "spotify"};
} }
} else { } 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;
} }

View file

@ -1,7 +1,8 @@
"use strict"; "use strict";
var parse = require('url').parse; var parse = require('url').parse;
var Promise = require('bluebird');
var request = require('superagent'); var request = require('superagent');
var Q = require('q'); require('superagent-bluebird-promise');
module.exports.id = "youtube"; module.exports.id = "youtube";
@ -19,7 +20,6 @@ var apiRoot = "https://www.googleapis.com/youtube/v3";
module.exports.match = require('./url').match; module.exports.match = require('./url').match;
module.exports.search = function(data) { module.exports.search = function(data) {
var deferred = Q.defer();
var query, album; var query, album;
var type = data.type; 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; 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]; var result = res.body.items[0];
if (!result) { if (!result) {
deferred.resolve({service:"youtube", type: "video"}); return {service:"youtube", type: "video"};
} else { } else {
deferred.resolve({ return {
service: "youtube", service: "youtube",
type: "video", type: "video",
id: result.id.videoId, id: result.id.videoId,
@ -47,8 +47,7 @@ module.exports.search = function(data) {
streamUrl: "https://www.youtube.com/watch?v=" + result.id.videoId, streamUrl: "https://www.youtube.com/watch?v=" + result.id.videoId,
purchaseUrl: null, purchaseUrl: null,
artwork: result.snippet.thumbnails.medium.url, artwork: result.snippet.thumbnails.medium.url,
}); };
} }
}); });
return deferred.promise;
}; };

View file

@ -14,13 +14,14 @@
"body-parser": "~1.8.1", "body-parser": "~1.8.1",
"connect-flash": "^0.1.1", "connect-flash": "^0.1.1",
"cookie-parser": "~1.3.3", "cookie-parser": "~1.3.3",
"crypto-js": "^3.1.2-5",
"debug": "~2.0.0", "debug": "~2.0.0",
"ejs": "~0.8.5", "ejs": "~0.8.5",
"express": "~4.9.0", "express": "~4.9.0",
"express-session": "^1.9.2", "express-session": "^1.9.2",
"helmet": "^0.5.2", "helmet": "^0.5.2",
"morgan": "~1.3.0", "morgan": "~1.3.0",
"playmusic": "^1.1.0", "node-uuid": "^1.4.2",
"promised-mongo": "^0.11.1", "promised-mongo": "^0.11.1",
"q": "^1.1.2", "q": "^1.1.2",
"rdio": "^1.5.2", "rdio": "^1.5.2",