Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

spotify: added support for published and unpublished rootlist types, added addTrack, added createPlaylist, add addToRootList #85

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,4 @@ See the `example` directory for some more example code.
API
---

TODO: document!
> Work in progress, see `./lib/spotify.js` for full API reference
8 changes: 4 additions & 4 deletions lib/schemas.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ var loadPackage = function(id) {
return new protobuf.Schema(fs.readFileSync(path.resolve(protoPath, schema + '.desc')));
});
return packageCache[id];

} else { // protobufjs
// Generate a proto string with import statements
var proto = mapping.map(function(schema) {
Expand All @@ -73,13 +73,13 @@ var loadPackage = function(id) {

var loadMessage = module.exports.build = function(packageId, messageId) {
debug('loadMessage(%j, %j) [%s]', packageId, messageId, library);

var packageObj = loadPackage(packageId);
var messageObj = null;

if (protobuf) {
var identifier = "spotify." + packageId + ".proto." + messageId;

// Loop though each loaded schema looking for the message
for (var i = 0; i < packageObj.length; i++) {
messageObj = packageObj[i][identifier];
Expand Down
143 changes: 141 additions & 2 deletions lib/spotify.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ module.exports = Spotify;
var MercuryMultiGetRequest = schemas.build('mercury','MercuryMultiGetRequest');
var MercuryMultiGetReply = schemas.build('mercury','MercuryMultiGetReply');
var MercuryRequest = schemas.build('mercury','MercuryRequest');
var MercuryReply = schemas.build('mercury','MercuryReply');

var Artist = require('./artist');
var Album = require('./album');
Expand All @@ -36,6 +37,8 @@ var Image = require('./image');
require('./restriction');

var SelectedListContent = schemas.build('playlist4','SelectedListContent');
var Op = schemas.build('playlist4', 'Op');
var CreateListReply = schemas.build('playlist4', 'CreateListReply');

var StoryRequest = schemas.build('bartender','StoryRequest');
var StoryList = schemas.build('bartender','StoryList');
Expand Down Expand Up @@ -782,6 +785,135 @@ Spotify.prototype.playlist = function (uri, from, length, fn) {
}, fn);
};

/**
* Add track to a playlist.
*
* @param {String} uri playlist uri
* @param {String} uri track uri.
* @param {Function} fn callback function
* @api public
*/

Spotify.prototype.addTrack = function (playlisturi, trackuri, fn) {
var self = this;
var playlist = playlisturi.split(':');
var user = playlist[2];
var playlistid = (playlist[3] === 'starred') ? 'starred' : "playlist/" + playlist[4];

var hm = 'hm://playlist/user/' + user + '/' + playlistid + '?syncpublished=1';
var request = MercuryRequest.serialize({ body: 'ADD', uri: hm }).toString('base64');
var data = new Buffer(trackuri).toString('base64');

var args = [ 0, request, data ];

this.sendCommand('sp/hm_b64', args, function (err, res) {
if (err) return fn(err);

var data = res.result;
if (data.length >= 1) {
// success!
fn(err, trackuri);
} else {
// TODO: real error handling
var header = MercuryReply.parse(new Buffer(res.result[0], 'base64'));
fn(err, null);
}

});
};

/**
* Add a playlist to rootlist
*
* @param {String} username
* @param {String} uri playlist uri.
* @param {Function} fn callback function
* @api public
*/

Spotify.prototype.addRootList = function (user, playlisturi, fn) {

var self = this;
var hm = 'hm://playlist/user/' + user + "/rootlist?add_first=1&syncpublished=1";
var request = MercuryRequest.serialize({ body: 'ADD', uri: hm }).toString('base64');
var data = new Buffer(playlisturi).toString('base64');

var args = [ 0, request, data ];

this.sendCommand('sp/hm_b64', args, function (err, res) {
if (err) return fn(err);

var data = res.result;

if (data.length >= 1) {
// success!
var obj = self._parse(CreateListReply, new Buffer(res.result[0], 'base64'));
fn(err, playlisturi);
} else {
// TODO: real error handling
var header = self._parse(MercuryReply, new Buffer(res.result[0], 'base64'));
fn(err, null);
}

});
};

// TODO: normalize as similar to <https://github.com/Hexxeh/spotify-websocket-api/blob/master/spotify_web/spotify.py#L583>

/**
* Create a playlist and add it to rootlist
*
* @param {String} username
* @param {String} playlist name
* @param {Function} fn callback function
* @api public
*/
Spotify.prototype.createPlaylist = function (user, name, fn) {

var self = this;
var hm = 'hm://playlist/user/' + user;

var request = MercuryRequest.serialize({
body: 'PUT',
uri: hm
}).toString('base64');

// kind values can be found in playlist4ops schema

var data = Op.serialize({
kind: 6,
updateListAttributes: {
newAttributes: {
values: {
name: name
}
}
}
});

// need to serialize everything, including data, with mercuryrequest
data = MercuryRequest.serialize({ uri : data }).toString('base64');

var args = [ 0, request, data ];

this.sendCommand('sp/hm_b64', args, function (err, res) {
if (err) return fn(err);
var obj;
var data = res.result;
if (data.length >= 1) {
// success!
obj = self._parse(CreateListReply, new Buffer(res.result[0], 'base64'));
self.addRootList(user, obj.uri.toString(), fn);
} else {
// TODO: real error handling
var header = self._parse(MercuryReply, new Buffer(res.result[0], 'base64'));
fn(err, null);
}

});

};

/**
* Gets a user's starred playlist
*
Expand Down Expand Up @@ -823,11 +955,12 @@ Spotify.prototype.starred = function (user, from, length, fn) {
* @param {String} user (optional) the username for the rootlist you want to retrieve. defaults to current user.
* @param {Number} from (optional) the start index. defaults to 0.
* @param {Number} length (optional) number of tracks to get. defaults to 100.
* @param {Boolean} published (optional) retrieve published playlists only
* @param {Function} fn callback function
* @api public
*/

Spotify.prototype.rootlist = function (user, from, length, fn) {
Spotify.prototype.rootlist = function (user, from, length, published, fn) {
// argument surgery
if ('function' == typeof user) {
fn = user;
Expand All @@ -838,15 +971,21 @@ Spotify.prototype.rootlist = function (user, from, length, fn) {
} else if ('function' == typeof length) {
fn = length;
length = null;
} else if ('function' == typeof published) {
fn = published
published = null
}
if (null == user) user = this.username;
if (null == from) from = 0;
if (null == length) length = 100;
if (null == published) published = false

var rootListType = !published ? 'rootlist' : 'publishedrootlist'

debug('rootlist(%j, %j, %j)', user, from, length);

var self = this;
var hm = 'hm://playlist/user/' + user + '/publishedrootlist?from=' + from + '&length=' + length;
var hm = 'hm://playlist/user/' + user + '/' + rootListType + '?from=' + from + '&length=' + length;

this.sendProtobufRequest({
header: {
Expand Down
9 changes: 4 additions & 5 deletions proto/mercury.desc
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@

�
mercury.protospotify.mercury.proto"P
�
proto/mercury.protospotify.mercury.proto"P
MercuryMultiGetRequest6
request ( 2%.spotify.mercury.proto.MercuryRequest"J
MercuryMultiGetReply2
Expand All @@ -11,16 +11,15 @@
method ( 
status_code (
source ( 5
user_fields ( 2 .spotify.mercury.proto.UserField"�
user_fields ( 2 .spotify.mercury.proto.UserField"�
MercuryReply
status_code (
status_message ( E
cache_policy (2/.spotify.mercury.proto.MercuryReply.CachePolicy
ttl (
etag ( 
content_type ( 
body ( 5
user_fields ( 2 .spotify.mercury.proto.UserField"@
body ( "@
CachePolicy
CACHE_NO
CACHE_PRIVATE
Expand Down
3 changes: 3 additions & 0 deletions proto/mercury.proto
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ message MercuryMultiGetReply {
message MercuryRequest {
optional string uri = 1;
optional string content_type = 2;
// per latest .xml?
//optional bytes body = 3;
//optional bytes etag = 4;
optional string method = 3;
optional sint32 status_code = 4;
optional string source = 5;
Expand Down