function Spotify() { if (!(this instanceof Spotify)) return new Spotify(); EventEmitter.call(this); this.seq = 0; this.heartbeatInterval = 18E4; // 180s, from "spotify.web.client.js" this.agent = superagent.agent(); this.connected = false; // true after the WebSocket "connect" message is sent this._callbacks = Object.create(null); this.authServer = 'play.spotify.com'; this.authUrl = '/xhr/json/auth.php'; this.landingUrl = '/'; this.userAgent = 'Mozilla/5.0 (Chrome/13.37 compatible-ish) spotify-web/' + pkg.version; // base URLs for Image files like album artwork, artist prfiles, etc. // these values taken from "spotify.web.client.js" this.sourceUrl = 'https://d3rt1990lpmkn.cloudfront.net'; this.sourceUrls = { tiny: this.sourceUrl + '/60/', small: this.sourceUrl + '/120/', normal: this.sourceUrl + '/300/', large: this.sourceUrl + '/640/', avatar: this.sourceUrl + '/artist_image/' }; // mappings for the protobuf `enum Size` this.sourceUrls.DEFAULT = this.sourceUrls.normal; this.sourceUrls.SMALL = this.sourceUrls.tiny; this.sourceUrls.LARGE = this.sourceUrls.large; this.sourceUrls.XLARGE = this.sourceUrls.avatar; // WebSocket callbacks this._onopen = this._onopen.bind(this); this._onclose = this._onclose.bind(this); this._onmessage = this._onmessage.bind(this); // start the "heartbeat" once the WebSocket connection is established this.once('connect', this._startHeartbeat); // handle "message" commands... this.on('message', this._onmessagecommand); // needs to emulate Spotify's "CodeValidator" object this._context = vm.createContext(); this._context.reply = this._reply.bind(this); // binded callback for when user doesn't pass a callback function this._defaultCallback = this._defaultCallback.bind(this); }
n/a
album = function (values) { ProtoBuf.Builder.Message.call(this); var i, field; // Create fields on the object itself to allow setting and getting through Message#fieldname for (i=0; i<fields.length; i++) { field = fields[i]; this[field.name] = (field.repeated) ? [] : null; } // Set the default values for (i=0; i<fields.length; i++) { field = fields[i]; if (typeof field.options['default'] != 'undefined') { try { this.set(field.name, field.options['default']); // Should not throw } catch (e) { throw(new Error("[INTERNAL] "+e)); } } } // Set field values from a values object if (arguments.length == 1 && typeof values == 'object' && /* not another Message */ typeof values.encode != 'function' && /* not a repeated field */ !ProtoBuf.Util.isArray(values) && /* not a ByteBuffer */ !(values instanceof ByteBuffer) && /* not an ArrayBuffer */ !(values instanceof ArrayBuffer) && /* not a Long */ !(ProtoBuf.Long && values instanceof ProtoBuf.Long)) { var keys = Object.keys(values); for (i=0; i<keys.length; i++) { this.set(keys[i], values[keys[i]]); // May throw } // Else set field values from arguments, in correct order } else { for (i=0; i<arguments.length; i++) { if (i<fields.length) { this.set(fields[i].name, arguments[i]); // May throw } } } }
n/a
artist = function (values) { ProtoBuf.Builder.Message.call(this); var i, field; // Create fields on the object itself to allow setting and getting through Message#fieldname for (i=0; i<fields.length; i++) { field = fields[i]; this[field.name] = (field.repeated) ? [] : null; } // Set the default values for (i=0; i<fields.length; i++) { field = fields[i]; if (typeof field.options['default'] != 'undefined') { try { this.set(field.name, field.options['default']); // Should not throw } catch (e) { throw(new Error("[INTERNAL] "+e)); } } } // Set field values from a values object if (arguments.length == 1 && typeof values == 'object' && /* not another Message */ typeof values.encode != 'function' && /* not a repeated field */ !ProtoBuf.Util.isArray(values) && /* not a ByteBuffer */ !(values instanceof ByteBuffer) && /* not an ArrayBuffer */ !(values instanceof ArrayBuffer) && /* not a Long */ !(ProtoBuf.Long && values instanceof ProtoBuf.Long)) { var keys = Object.keys(values); for (i=0; i<keys.length; i++) { this.set(keys[i], values[keys[i]]); // May throw } // Else set field values from arguments, in correct order } else { for (i=0; i<arguments.length; i++) { if (i<fields.length) { this.set(fields[i].name, arguments[i]); // May throw } } } }
n/a
function SpotifyError(err) { this.domain = err[0] || 0; this.code = err[1] || 0; this.description = err[2] || ''; this.data = err[3] || null; // Error impl this.name = domains[this.domain]; var msg = codes[this.code]; if (this.description) msg += ' (' + this.description + ')'; this.message = msg; Error.captureStackTrace(this, SpotifyError); }
n/a
gid2id = function (gid) { for (var b = '', c = 0, a = gid.length; c < a; ++c) { b += (gid[c] + 256).toString(16).slice(-2); } return b; }
...
/**
* Track Preview URL getter
*/
Object.defineProperty(Track.prototype, 'previewUrl', {
get: function () {
var previewUrlBase = 'http://d318706lgtcm8e.cloudfront.net/mp3-preview/'
return this.preview.length && (previewUrlBase + util.gid2id(this.preview
[0].fileId));
},
enumerable: true,
configurable: true
})
/**
* Loads all the metadata for this Track instance. Useful for when you get an only
...
gid2uri = function (uriType, gid) { var id = exports.gid2id(gid); return exports.id2uri(uriType, id); }
...
/**
* Album URI getter.
*/
Object.defineProperty(Album.prototype, 'uri', {
get: function () {
return util.gid2uri('album', this.gid);
},
enumerable: true,
configurable: true
});
/**
* Loads all the metadata for this Album instance. Useful for when you get an only
...
id2uri = function (uriType, v) { var id = base62.fromHex(v, 22); return 'spotify:' + uriType + ':' + id; }
...
/**
* GID -> URI
*/
exports.gid2uri = function (uriType, gid) {
var id = exports.gid2id(gid);
return exports.id2uri(uriType, id);
};
/**
* Accepts a String URI, returns the "type" of URI.
* i.e. one of "local", "playlist", "track", etc.
*/
...
image = function (values) { ProtoBuf.Builder.Message.call(this); var i, field; // Create fields on the object itself to allow setting and getting through Message#fieldname for (i=0; i<fields.length; i++) { field = fields[i]; this[field.name] = (field.repeated) ? [] : null; } // Set the default values for (i=0; i<fields.length; i++) { field = fields[i]; if (typeof field.options['default'] != 'undefined') { try { this.set(field.name, field.options['default']); // Should not throw } catch (e) { throw(new Error("[INTERNAL] "+e)); } } } // Set field values from a values object if (arguments.length == 1 && typeof values == 'object' && /* not another Message */ typeof values.encode != 'function' && /* not a repeated field */ !ProtoBuf.Util.isArray(values) && /* not a ByteBuffer */ !(values instanceof ByteBuffer) && /* not an ArrayBuffer */ !(values instanceof ArrayBuffer) && /* not a Long */ !(ProtoBuf.Long && values instanceof ProtoBuf.Long)) { var keys = Object.keys(values); for (i=0; i<keys.length; i++) { this.set(keys[i], values[keys[i]]); // May throw } // Else set field values from arguments, in correct order } else { for (i=0; i<arguments.length; i++) { if (i<fields.length) { this.set(fields[i].name, arguments[i]); // May throw } } } }
n/a
login = function (un, pw, fn) { if (!fn) fn = function () {}; var spotify = new Spotify(); spotify.login(un, pw, function (err) { if (err) return fn(err); fn.call(spotify, null, spotify); }); return spotify; }
...
var Spotify = require('spotify-web');
var uri = process.argv[2] || 'spotify:track:6tdp8sdXrXlPV6AZZN2PE8';
// Spotify credentials...
var username = process.env.USERNAME;
var password = process.env.PASSWORD;
Spotify.login(username, password, function (err, spotify) {
if (err) throw err;
// first get a "Track" instance from the track URI
spotify.get(uri, function (err, track) {
if (err) throw err;
console.log('Playing: %s - %s', track.artist[0].name, track.name);
...
restriction = function (values) { ProtoBuf.Builder.Message.call(this); var i, field; // Create fields on the object itself to allow setting and getting through Message#fieldname for (i=0; i<fields.length; i++) { field = fields[i]; this[field.name] = (field.repeated) ? [] : null; } // Set the default values for (i=0; i<fields.length; i++) { field = fields[i]; if (typeof field.options['default'] != 'undefined') { try { this.set(field.name, field.options['default']); // Should not throw } catch (e) { throw(new Error("[INTERNAL] "+e)); } } } // Set field values from a values object if (arguments.length == 1 && typeof values == 'object' && /* not another Message */ typeof values.encode != 'function' && /* not a repeated field */ !ProtoBuf.Util.isArray(values) && /* not a ByteBuffer */ !(values instanceof ByteBuffer) && /* not an ArrayBuffer */ !(values instanceof ArrayBuffer) && /* not a Long */ !(ProtoBuf.Long && values instanceof ProtoBuf.Long)) { var keys = Object.keys(values); for (i=0; i<keys.length; i++) { this.set(keys[i], values[keys[i]]); // May throw } // Else set field values from arguments, in correct order } else { for (i=0; i<arguments.length; i++) { if (i<fields.length) { this.set(fields[i].name, arguments[i]); // May throw } } } }
n/a
function Spotify() { if (!(this instanceof Spotify)) return new Spotify(); EventEmitter.call(this); this.seq = 0; this.heartbeatInterval = 18E4; // 180s, from "spotify.web.client.js" this.agent = superagent.agent(); this.connected = false; // true after the WebSocket "connect" message is sent this._callbacks = Object.create(null); this.authServer = 'play.spotify.com'; this.authUrl = '/xhr/json/auth.php'; this.landingUrl = '/'; this.userAgent = 'Mozilla/5.0 (Chrome/13.37 compatible-ish) spotify-web/' + pkg.version; // base URLs for Image files like album artwork, artist prfiles, etc. // these values taken from "spotify.web.client.js" this.sourceUrl = 'https://d3rt1990lpmkn.cloudfront.net'; this.sourceUrls = { tiny: this.sourceUrl + '/60/', small: this.sourceUrl + '/120/', normal: this.sourceUrl + '/300/', large: this.sourceUrl + '/640/', avatar: this.sourceUrl + '/artist_image/' }; // mappings for the protobuf `enum Size` this.sourceUrls.DEFAULT = this.sourceUrls.normal; this.sourceUrls.SMALL = this.sourceUrls.tiny; this.sourceUrls.LARGE = this.sourceUrls.large; this.sourceUrls.XLARGE = this.sourceUrls.avatar; // WebSocket callbacks this._onopen = this._onopen.bind(this); this._onclose = this._onclose.bind(this); this._onmessage = this._onmessage.bind(this); // start the "heartbeat" once the WebSocket connection is established this.once('connect', this._startHeartbeat); // handle "message" commands... this.on('message', this._onmessagecommand); // needs to emulate Spotify's "CodeValidator" object this._context = vm.createContext(); this._context.reply = this._reply.bind(this); // binded callback for when user doesn't pass a callback function this._defaultCallback = this._defaultCallback.bind(this); }
n/a
function EventEmitter() { EventEmitter.init.call(this); }
n/a
track = function (values) { ProtoBuf.Builder.Message.call(this); var i, field; // Create fields on the object itself to allow setting and getting through Message#fieldname for (i=0; i<fields.length; i++) { field = fields[i]; this[field.name] = (field.repeated) ? [] : null; } // Set the default values for (i=0; i<fields.length; i++) { field = fields[i]; if (typeof field.options['default'] != 'undefined') { try { this.set(field.name, field.options['default']); // Should not throw } catch (e) { throw(new Error("[INTERNAL] "+e)); } } } // Set field values from a values object if (arguments.length == 1 && typeof values == 'object' && /* not another Message */ typeof values.encode != 'function' && /* not a repeated field */ !ProtoBuf.Util.isArray(values) && /* not a ByteBuffer */ !(values instanceof ByteBuffer) && /* not an ArrayBuffer */ !(values instanceof ArrayBuffer) && /* not a Long */ !(ProtoBuf.Long && values instanceof ProtoBuf.Long)) { var keys = Object.keys(values); for (i=0; i<keys.length; i++) { this.set(keys[i], values[keys[i]]); // May throw } // Else set field values from arguments, in correct order } else { for (i=0; i<arguments.length; i++) { if (i<fields.length) { this.set(fields[i].name, arguments[i]); // May throw } } } }
n/a
uri2id = function (uri) { var parts = uri.split(':'); var s; if (parts.length > 3 && 'playlist' == parts[3]) { s = parts[4]; } else { s = parts[2]; } var v = base62.toHex(s); return v; }
...
var id = base62.fromHex(v, 22);
return 'spotify:' + uriType + ':' + id;
};
/**
* URI -> ID
*
* >>> SpotifyUtil.uri2id('spotify:track:6tdp8sdXrXlPV6AZZN2PE8
x27;)
* 'd49fcea60d1f450691669b67af3bda24'
* >>> SpotifyUtil.uri2id('spotify:user:tootallnate:playlist:0Lt5S4hGarhtZmtz7BNTeX')
* '192803a20370c0995f271891a32da6a3'
*/
exports.uri2id = function (uri) {
var parts = uri.split(':');
...
uriType = function (uri) { var parts = uri.split(':'); var len = parts.length; if (len >= 3 && 'local' == parts[1]) { return 'local'; } else if (len >= 5) { return parts[3]; } else if (len >= 4 && 'starred' == parts[3]) { return 'playlist'; } else if (len >= 3) { return parts[1]; } else { throw new Error('could not determine "type" for URI: ' + uri); } }
n/a
album = function (values) { ProtoBuf.Builder.Message.call(this); var i, field; // Create fields on the object itself to allow setting and getting through Message#fieldname for (i=0; i<fields.length; i++) { field = fields[i]; this[field.name] = (field.repeated) ? [] : null; } // Set the default values for (i=0; i<fields.length; i++) { field = fields[i]; if (typeof field.options['default'] != 'undefined') { try { this.set(field.name, field.options['default']); // Should not throw } catch (e) { throw(new Error("[INTERNAL] "+e)); } } } // Set field values from a values object if (arguments.length == 1 && typeof values == 'object' && /* not another Message */ typeof values.encode != 'function' && /* not a repeated field */ !ProtoBuf.Util.isArray(values) && /* not a ByteBuffer */ !(values instanceof ByteBuffer) && /* not an ArrayBuffer */ !(values instanceof ArrayBuffer) && /* not a Long */ !(ProtoBuf.Long && values instanceof ProtoBuf.Long)) { var keys = Object.keys(values); for (i=0; i<keys.length; i++) { this.set(keys[i], values[keys[i]]); // May throw } // Else set field values from arguments, in correct order } else { for (i=0; i<arguments.length; i++) { if (i<fields.length) { this.set(fields[i].name, arguments[i]); // May throw } } } }
n/a
decode = function (buffer, enc) { if (buffer === null) throw(new Error("buffer must not be null")); if (typeof buffer === 'string') { buffer = ByteBuffer.wrap(buffer, enc ? enc : "base64"); } buffer = buffer instanceof ByteBuffer ? buffer : ByteBuffer.wrap(buffer); // May throw var le = buffer.littleEndian; try { var msg = T.decode(buffer.LE()); buffer.LE(le); return msg; } catch (e) { buffer.LE(le); throw(e); } }
n/a
decode64 = function (str) { return Message.decode(str, "base64"); }
n/a
decodeHex = function (str) { return Message.decode(str, "hex"); }
n/a
function protobufjs_parse_wrapper() { debug('protobufjs_parse_wrapper(%j)', arguments); // Call the message object decode function with the arguments var message = messageObj.decode.apply(null, arguments); // Convert the object keys to camel case, ByteBuffers to Node Buffers and then return the parsed object return convertByteBuffersToNodeBuffers(reCamelCase(message)); }
n/a
function protobufjs_serialize_wrapper() { debug('protobufjs_serialize_wrapper(%j)', arguments); // Convert any camel cased properties in the arguments to underscored properties Array.prototype.map.call(arguments, function (argument) { return deCamelCase(argument); }); // Call the message object constructor with the modified arguments var message = Object.create(messageObj.prototype); message = messageObj.apply(message, arguments) || message; // Return the node Buffer object containing the serialised data return message.encodeNB(); }
n/a
add = function (key, value) { var field = T.getChild(key); if (!field) { throw(new Error(this+"#"+key+" is undefined")); } if (!(field instanceof ProtoBuf.Reflect.Message.Field)) { throw(new Error(this+"#"+key+" is not a field: "+field.toString(true))); // May throw if it's an enum or embedded message } if (!field.repeated) { throw(new Error(this+"#"+key+" is not a repeated field")); } if (this[field.name] === null) this[field.name] = []; this[field.name].push(field.verifyValue(value, true)); }
n/a
encode = function (buffer) { buffer = buffer || new ByteBuffer(); var le = buffer.littleEndian; try { return T.encode(this, buffer.LE()).flip().LE(le); } catch (e) { buffer.LE(le); throw(e); } }
n/a
encode64 = function () { try { return this.encode().toBase64(); } catch (err) { if (err["encoded"]) err["encoded"] = err["encoded"].toBase64(); throw(err); } }
n/a
encodeAB = function () { var enc; try { return this.encode().toArrayBuffer(); } catch (err) { if (err["encoded"]) err["encoded"] = err["encoded"].toArrayBuffer(); throw(err); } }
n/a
encodeHex = function () { try { return this.encode().toHex(); } catch (err) { if (err["encoded"]) err["encoded"] = err["encoded"].toHex(); throw(err); } }
n/a
encodeNB = function () { try { return this.encode().toBuffer(); } catch (err) { if (err["encoded"]) err["encoded"] = err["encoded"].toBuffer(); throw(err); } }
...
});
// Call the message object constructor with the modified arguments
var message = Object.create(messageObj.prototype);
message = messageObj.apply(message, arguments) || message;
// Return the node Buffer object containing the serialised data
return message.encodeNB();
};
}
return messageObj;
};
var deCamelCase = function(obj) {
...
get = function (fn) { if (this._loaded) { // already been loaded... debug('album already loaded'); return process.nextTick(fn.bind(null, null, this)); } var spotify = this._spotify; var self = this; spotify.get(this.uri, function (err, album) { if (err) return fn(err); // extend this Album instance with the new one's properties Object.keys(album).forEach(function (key) { if (!self.hasOwnProperty(key)) { self[key] = album[key]; } }); fn(null, self); }); }
...
var username = process.env.USERNAME;
var password = process.env.PASSWORD;
Spotify.login(username, password, function (err, spotify) {
if (err) throw err;
// first get a "Track" instance from the track URI
spotify.get(uri, function (err, track) {
if (err) throw err;
console.log('Playing: %s - %s', track.artist[0].name, track.name);
// play() returns a readable stream of MP3 audio data
track.play()
.pipe(new lame.Decoder())
.pipe(new Speaker())
...
getArtist = function () { return this.get(field.name); // Does not throw, field exists }
n/a
getCopyright = function () { return this.get(field.name); // Does not throw, field exists }
n/a
getCover = function () { return this.get(field.name); // Does not throw, field exists }
n/a
getCoverGroup = function () { return this.get(field.name); // Does not throw, field exists }
n/a
getDate = function () { return this.get(field.name); // Does not throw, field exists }
n/a
getDisc = function () { return this.get(field.name); // Does not throw, field exists }
n/a
getExternalId = function () { return this.get(field.name); // Does not throw, field exists }
n/a
getGenre = function () { return this.get(field.name); // Does not throw, field exists }
n/a
getGid = function () { return this.get(field.name); // Does not throw, field exists }
n/a
getLabel = function () { return this.get(field.name); // Does not throw, field exists }
n/a
getName = function () { return this.get(field.name); // Does not throw, field exists }
n/a
getPopularity = function () { return this.get(field.name); // Does not throw, field exists }
n/a
getRelated = function () { return this.get(field.name); // Does not throw, field exists }
n/a
getRestriction = function () { return this.get(field.name); // Does not throw, field exists }
n/a
getReview = function () { return this.get(field.name); // Does not throw, field exists }
n/a
getSalePeriod = function () { return this.get(field.name); // Does not throw, field exists }
n/a
getType = function () { return this.get(field.name); // Does not throw, field exists }
n/a
get_artist = function () { return this.get(field.name); // Does not throw, field exists }
n/a
get_copyright = function () { return this.get(field.name); // Does not throw, field exists }
n/a
get_cover = function () { return this.get(field.name); // Does not throw, field exists }
n/a
get_cover_group = function () { return this.get(field.name); // Does not throw, field exists }
n/a
get_date = function () { return this.get(field.name); // Does not throw, field exists }
n/a
get_disc = function () { return this.get(field.name); // Does not throw, field exists }
n/a
get_external_id = function () { return this.get(field.name); // Does not throw, field exists }
n/a
get_genre = function () { return this.get(field.name); // Does not throw, field exists }
n/a
get_gid = function () { return this.get(field.name); // Does not throw, field exists }
n/a
get_label = function () { return this.get(field.name); // Does not throw, field exists }
n/a
get_name = function () { return this.get(field.name); // Does not throw, field exists }
n/a
get_popularity = function () { return this.get(field.name); // Does not throw, field exists }
n/a
get_related = function () { return this.get(field.name); // Does not throw, field exists }
n/a
get_restriction = function () { return this.get(field.name); // Does not throw, field exists }
n/a
get_review = function () { return this.get(field.name); // Does not throw, field exists }
n/a
get_sale_period = function () { return this.get(field.name); // Does not throw, field exists }
n/a
get_type = function () { return this.get(field.name); // Does not throw, field exists }
n/a
metadata = function (fn) { if (this._loaded) { // already been loaded... debug('album already loaded'); return process.nextTick(fn.bind(null, null, this)); } var spotify = this._spotify; var self = this; spotify.get(this.uri, function (err, album) { if (err) return fn(err); // extend this Album instance with the new one's properties Object.keys(album).forEach(function (key) { if (!self.hasOwnProperty(key)) { self[key] = album[key]; } }); fn(null, self); }); }
n/a
set = function (key, value) { var field = T.getChild(key); if (!field) { throw(new Error(this+"#"+key+" is not a field: undefined")); } if (!(field instanceof ProtoBuf.Reflect.Message.Field)) { throw(new Error(this+"#"+key+" is not a field: "+field.toString(true))); } this[field.name] = field.verifyValue(value); // May throw }
...
// initiate a "play session" for this Track
spotify.trackUri(track, function (err, res) {
if (err) return stream.emit('error', err);
if (!res.uri) return stream.emit('error', new Error('response contained no "uri"'));
debug('GET %s', res.uri);
track._playSession = res;
var req = spotify.agent.get(res.uri)
.set({ 'User-Agent': spotify.userAgent })
.end()
.request();
req.on('response', response);
});
function response (res) {
debug('HTTP/%s %s', res.httpVersion, res.statusCode);
...
setArtist = function (value) { this.set(field.name, value); }
n/a
setCopyright = function (value) { this.set(field.name, value); }
n/a
setCover = function (value) { this.set(field.name, value); }
n/a
setCoverGroup = function (value) { this.set(field.name, value); }
n/a
setDate = function (value) { this.set(field.name, value); }
n/a
setDisc = function (value) { this.set(field.name, value); }
n/a
setExternalId = function (value) { this.set(field.name, value); }
n/a
setGenre = function (value) { this.set(field.name, value); }
n/a
setGid = function (value) { this.set(field.name, value); }
n/a
setLabel = function (value) { this.set(field.name, value); }
n/a
setName = function (value) { this.set(field.name, value); }
n/a
setPopularity = function (value) { this.set(field.name, value); }
n/a
setRelated = function (value) { this.set(field.name, value); }
n/a
setRestriction = function (value) { this.set(field.name, value); }
n/a
setReview = function (value) { this.set(field.name, value); }
n/a
setSalePeriod = function (value) { this.set(field.name, value); }
n/a
setType = function (value) { this.set(field.name, value); }
n/a
set_artist = function (value) { this.set(field.name, value); }
n/a
set_copyright = function (value) { this.set(field.name, value); }
n/a
set_cover = function (value) { this.set(field.name, value); }
n/a
set_cover_group = function (value) { this.set(field.name, value); }
n/a
set_date = function (value) { this.set(field.name, value); }
n/a
set_disc = function (value) { this.set(field.name, value); }
n/a
set_external_id = function (value) { this.set(field.name, value); }
n/a
set_genre = function (value) { this.set(field.name, value); }
n/a
set_gid = function (value) { this.set(field.name, value); }
n/a
set_label = function (value) { this.set(field.name, value); }
n/a
set_name = function (value) { this.set(field.name, value); }
n/a
set_popularity = function (value) { this.set(field.name, value); }
n/a
set_related = function (value) { this.set(field.name, value); }
n/a
set_restriction = function (value) { this.set(field.name, value); }
n/a
set_review = function (value) { this.set(field.name, value); }
n/a
set_sale_period = function (value) { this.set(field.name, value); }
n/a
set_type = function (value) { this.set(field.name, value); }
n/a
toArrayBuffer = function () { var enc; try { return this.encode().toArrayBuffer(); } catch (err) { if (err["encoded"]) err["encoded"] = err["encoded"].toArrayBuffer(); throw(err); } }
n/a
toBase64 = function () { try { return this.encode().toBase64(); } catch (err) { if (err["encoded"]) err["encoded"] = err["encoded"].toBase64(); throw(err); } }
n/a
toBuffer = function () { try { return this.encode().toBuffer(); } catch (err) { if (err["encoded"]) err["encoded"] = err["encoded"].toBuffer(); throw(err); } }
...
};
var convertByteBuffersToNodeBuffers = function(obj) {
if (obj === null || 'object' != typeof obj) return obj;
Object.keys(obj).forEach(function(key) {
// attempt to detect a bytebuffer object
if (obj[key] && obj[key].hasOwnProperty('array') && obj[key].hasOwnProperty('view')) {
obj[key] = obj[key].toBuffer();
} else {
obj[key] = convertByteBuffersToNodeBuffers(obj[key]);
}
});
return obj;
};
...
toHex = function () { try { return this.encode().toHex(); } catch (err) { if (err["encoded"]) err["encoded"] = err["encoded"].toHex(); throw(err); } }
...
var parts = uri.split(':');
var s;
if (parts.length > 3 && 'playlist' == parts[3]) {
s = parts[4];
} else {
s = parts[2];
}
var v = base62.toHex(s);
return v;
};
/**
* GID -> URI
*/
...
toString = function () { return T.toString(); }
...
/**
* Converts a GID Buffer to an ID hex string.
* Based off of Spotify.Utils.str2hex(), modified to work with Buffers.
*/
exports.gid2id = function (gid) {
for (var b = '', c = 0, a = gid.length; c < a; ++c) {
b += (gid[c] + 256).toString(16).slice(-2);
}
return b;
};
/**
* ID -> URI
*/
...
artist = function (values) { ProtoBuf.Builder.Message.call(this); var i, field; // Create fields on the object itself to allow setting and getting through Message#fieldname for (i=0; i<fields.length; i++) { field = fields[i]; this[field.name] = (field.repeated) ? [] : null; } // Set the default values for (i=0; i<fields.length; i++) { field = fields[i]; if (typeof field.options['default'] != 'undefined') { try { this.set(field.name, field.options['default']); // Should not throw } catch (e) { throw(new Error("[INTERNAL] "+e)); } } } // Set field values from a values object if (arguments.length == 1 && typeof values == 'object' && /* not another Message */ typeof values.encode != 'function' && /* not a repeated field */ !ProtoBuf.Util.isArray(values) && /* not a ByteBuffer */ !(values instanceof ByteBuffer) && /* not an ArrayBuffer */ !(values instanceof ArrayBuffer) && /* not a Long */ !(ProtoBuf.Long && values instanceof ProtoBuf.Long)) { var keys = Object.keys(values); for (i=0; i<keys.length; i++) { this.set(keys[i], values[keys[i]]); // May throw } // Else set field values from arguments, in correct order } else { for (i=0; i<arguments.length; i++) { if (i<fields.length) { this.set(fields[i].name, arguments[i]); // May throw } } } }
n/a
decode = function (buffer, enc) { if (buffer === null) throw(new Error("buffer must not be null")); if (typeof buffer === 'string') { buffer = ByteBuffer.wrap(buffer, enc ? enc : "base64"); } buffer = buffer instanceof ByteBuffer ? buffer : ByteBuffer.wrap(buffer); // May throw var le = buffer.littleEndian; try { var msg = T.decode(buffer.LE()); buffer.LE(le); return msg; } catch (e) { buffer.LE(le); throw(e); } }
n/a
decode64 = function (str) { return Message.decode(str, "base64"); }
n/a
decodeHex = function (str) { return Message.decode(str, "hex"); }
n/a
function protobufjs_parse_wrapper() { debug('protobufjs_parse_wrapper(%j)', arguments); // Call the message object decode function with the arguments var message = messageObj.decode.apply(null, arguments); // Convert the object keys to camel case, ByteBuffers to Node Buffers and then return the parsed object return convertByteBuffersToNodeBuffers(reCamelCase(message)); }
n/a
function protobufjs_serialize_wrapper() { debug('protobufjs_serialize_wrapper(%j)', arguments); // Convert any camel cased properties in the arguments to underscored properties Array.prototype.map.call(arguments, function (argument) { return deCamelCase(argument); }); // Call the message object constructor with the modified arguments var message = Object.create(messageObj.prototype); message = messageObj.apply(message, arguments) || message; // Return the node Buffer object containing the serialised data return message.encodeNB(); }
n/a
add = function (key, value) { var field = T.getChild(key); if (!field) { throw(new Error(this+"#"+key+" is undefined")); } if (!(field instanceof ProtoBuf.Reflect.Message.Field)) { throw(new Error(this+"#"+key+" is not a field: "+field.toString(true))); // May throw if it's an enum or embedded message } if (!field.repeated) { throw(new Error(this+"#"+key+" is not a repeated field")); } if (this[field.name] === null) this[field.name] = []; this[field.name].push(field.verifyValue(value, true)); }
n/a
encode = function (buffer) { buffer = buffer || new ByteBuffer(); var le = buffer.littleEndian; try { return T.encode(this, buffer.LE()).flip().LE(le); } catch (e) { buffer.LE(le); throw(e); } }
n/a
encode64 = function () { try { return this.encode().toBase64(); } catch (err) { if (err["encoded"]) err["encoded"] = err["encoded"].toBase64(); throw(err); } }
n/a
encodeAB = function () { var enc; try { return this.encode().toArrayBuffer(); } catch (err) { if (err["encoded"]) err["encoded"] = err["encoded"].toArrayBuffer(); throw(err); } }
n/a
encodeHex = function () { try { return this.encode().toHex(); } catch (err) { if (err["encoded"]) err["encoded"] = err["encoded"].toHex(); throw(err); } }
n/a
encodeNB = function () { try { return this.encode().toBuffer(); } catch (err) { if (err["encoded"]) err["encoded"] = err["encoded"].toBuffer(); throw(err); } }
...
});
// Call the message object constructor with the modified arguments
var message = Object.create(messageObj.prototype);
message = messageObj.apply(message, arguments) || message;
// Return the node Buffer object containing the serialised data
return message.encodeNB();
};
}
return messageObj;
};
var deCamelCase = function(obj) {
...
get = function (fn) { if (this._loaded) { // already been loaded... debug('artist already loaded'); return process.nextTick(fn.bind(null, null, this)); } var spotify = this._spotify; var self = this; spotify.get(this.uri, function (err, artist) { if (err) return fn(err); // extend this Artist instance with the new one's properties Object.keys(artist).forEach(function (key) { if (!self.hasOwnProperty(key)) { self[key] = artist[key]; } }); fn(null, self); }); }
...
var username = process.env.USERNAME;
var password = process.env.PASSWORD;
Spotify.login(username, password, function (err, spotify) {
if (err) throw err;
// first get a "Track" instance from the track URI
spotify.get(uri, function (err, track) {
if (err) throw err;
console.log('Playing: %s - %s', track.artist[0].name, track.name);
// play() returns a readable stream of MP3 audio data
track.play()
.pipe(new lame.Decoder())
.pipe(new Speaker())
...
getActivityPeriod = function () { return this.get(field.name); // Does not throw, field exists }
n/a
getAlbumGroup = function () { return this.get(field.name); // Does not throw, field exists }
n/a
getAppearsOnGroup = function () { return this.get(field.name); // Does not throw, field exists }
n/a
getBiography = function () { return this.get(field.name); // Does not throw, field exists }
n/a
getCompilationGroup = function () { return this.get(field.name); // Does not throw, field exists }
n/a
getExternalId = function () { return this.get(field.name); // Does not throw, field exists }
n/a
getGenre = function () { return this.get(field.name); // Does not throw, field exists }
n/a
getGid = function () { return this.get(field.name); // Does not throw, field exists }
n/a
getIsPortraitAlbumCover = function () { return this.get(field.name); // Does not throw, field exists }
n/a
getName = function () { return this.get(field.name); // Does not throw, field exists }
n/a
getPopularity = function () { return this.get(field.name); // Does not throw, field exists }
n/a
getPortrait = function () { return this.get(field.name); // Does not throw, field exists }
n/a
getPortraitGroup = function () { return this.get(field.name); // Does not throw, field exists }
n/a
getRelated = function () { return this.get(field.name); // Does not throw, field exists }
n/a
getRestriction = function () { return this.get(field.name); // Does not throw, field exists }
n/a
getSingleGroup = function () { return this.get(field.name); // Does not throw, field exists }
n/a
getTopTrack = function () { return this.get(field.name); // Does not throw, field exists }
n/a
get_activity_period = function () { return this.get(field.name); // Does not throw, field exists }
n/a
get_album_group = function () { return this.get(field.name); // Does not throw, field exists }
n/a
get_appears_on_group = function () { return this.get(field.name); // Does not throw, field exists }
n/a
get_biography = function () { return this.get(field.name); // Does not throw, field exists }
n/a
get_compilation_group = function () { return this.get(field.name); // Does not throw, field exists }
n/a
get_external_id = function () { return this.get(field.name); // Does not throw, field exists }
n/a
get_genre = function () { return this.get(field.name); // Does not throw, field exists }
n/a
get_gid = function () { return this.get(field.name); // Does not throw, field exists }
n/a
get_is_portrait_album_cover = function () { return this.get(field.name); // Does not throw, field exists }
n/a
get_name = function () { return this.get(field.name); // Does not throw, field exists }
n/a
get_popularity = function () { return this.get(field.name); // Does not throw, field exists }
n/a
get_portrait = function () { return this.get(field.name); // Does not throw, field exists }
n/a
get_portrait_group = function () { return this.get(field.name); // Does not throw, field exists }
n/a
get_related = function () { return this.get(field.name); // Does not throw, field exists }
n/a
get_restriction = function () { return this.get(field.name); // Does not throw, field exists }
n/a
get_single_group = function () { return this.get(field.name); // Does not throw, field exists }
n/a
get_top_track = function () { return this.get(field.name); // Does not throw, field exists }
n/a
metadata = function (fn) { if (this._loaded) { // already been loaded... debug('artist already loaded'); return process.nextTick(fn.bind(null, null, this)); } var spotify = this._spotify; var self = this; spotify.get(this.uri, function (err, artist) { if (err) return fn(err); // extend this Artist instance with the new one's properties Object.keys(artist).forEach(function (key) { if (!self.hasOwnProperty(key)) { self[key] = artist[key]; } }); fn(null, self); }); }
n/a
set = function (key, value) { var field = T.getChild(key); if (!field) { throw(new Error(this+"#"+key+" is not a field: undefined")); } if (!(field instanceof ProtoBuf.Reflect.Message.Field)) { throw(new Error(this+"#"+key+" is not a field: "+field.toString(true))); } this[field.name] = field.verifyValue(value); // May throw }
...
// initiate a "play session" for this Track
spotify.trackUri(track, function (err, res) {
if (err) return stream.emit('error', err);
if (!res.uri) return stream.emit('error', new Error('response contained no "uri"'));
debug('GET %s', res.uri);
track._playSession = res;
var req = spotify.agent.get(res.uri)
.set({ 'User-Agent': spotify.userAgent })
.end()
.request();
req.on('response', response);
});
function response (res) {
debug('HTTP/%s %s', res.httpVersion, res.statusCode);
...
setActivityPeriod = function (value) { this.set(field.name, value); }
n/a
setAlbumGroup = function (value) { this.set(field.name, value); }
n/a
setAppearsOnGroup = function (value) { this.set(field.name, value); }
n/a
setBiography = function (value) { this.set(field.name, value); }
n/a
setCompilationGroup = function (value) { this.set(field.name, value); }
n/a
setExternalId = function (value) { this.set(field.name, value); }
n/a
setGenre = function (value) { this.set(field.name, value); }
n/a
setGid = function (value) { this.set(field.name, value); }
n/a
setIsPortraitAlbumCover = function (value) { this.set(field.name, value); }
n/a
setName = function (value) { this.set(field.name, value); }
n/a
setPopularity = function (value) { this.set(field.name, value); }
n/a
setPortrait = function (value) { this.set(field.name, value); }
n/a
setPortraitGroup = function (value) { this.set(field.name, value); }
n/a
setRelated = function (value) { this.set(field.name, value); }
n/a
setRestriction = function (value) { this.set(field.name, value); }
n/a
setSingleGroup = function (value) { this.set(field.name, value); }
n/a
setTopTrack = function (value) { this.set(field.name, value); }
n/a
set_activity_period = function (value) { this.set(field.name, value); }
n/a
set_album_group = function (value) { this.set(field.name, value); }
n/a
set_appears_on_group = function (value) { this.set(field.name, value); }
n/a
set_biography = function (value) { this.set(field.name, value); }
n/a
set_compilation_group = function (value) { this.set(field.name, value); }
n/a
set_external_id = function (value) { this.set(field.name, value); }
n/a
set_genre = function (value) { this.set(field.name, value); }
n/a
set_gid = function (value) { this.set(field.name, value); }
n/a
set_is_portrait_album_cover = function (value) { this.set(field.name, value); }
n/a
set_name = function (value) { this.set(field.name, value); }
n/a
set_popularity = function (value) { this.set(field.name, value); }
n/a
set_portrait = function (value) { this.set(field.name, value); }
n/a
set_portrait_group = function (value) { this.set(field.name, value); }
n/a
set_related = function (value) { this.set(field.name, value); }
n/a
set_restriction = function (value) { this.set(field.name, value); }
n/a
set_single_group = function (value) { this.set(field.name, value); }
n/a
set_top_track = function (value) { this.set(field.name, value); }
n/a
toArrayBuffer = function () { var enc; try { return this.encode().toArrayBuffer(); } catch (err) { if (err["encoded"]) err["encoded"] = err["encoded"].toArrayBuffer(); throw(err); } }
n/a
toBase64 = function () { try { return this.encode().toBase64(); } catch (err) { if (err["encoded"]) err["encoded"] = err["encoded"].toBase64(); throw(err); } }
n/a
toBuffer = function () { try { return this.encode().toBuffer(); } catch (err) { if (err["encoded"]) err["encoded"] = err["encoded"].toBuffer(); throw(err); } }
...
};
var convertByteBuffersToNodeBuffers = function(obj) {
if (obj === null || 'object' != typeof obj) return obj;
Object.keys(obj).forEach(function(key) {
// attempt to detect a bytebuffer object
if (obj[key] && obj[key].hasOwnProperty('array') && obj[key].hasOwnProperty('view')) {
obj[key] = obj[key].toBuffer();
} else {
obj[key] = convertByteBuffersToNodeBuffers(obj[key]);
}
});
return obj;
};
...
toHex = function () { try { return this.encode().toHex(); } catch (err) { if (err["encoded"]) err["encoded"] = err["encoded"].toHex(); throw(err); } }
...
var parts = uri.split(':');
var s;
if (parts.length > 3 && 'playlist' == parts[3]) {
s = parts[4];
} else {
s = parts[2];
}
var v = base62.toHex(s);
return v;
};
/**
* GID -> URI
*/
...
toString = function () { return T.toString(); }
...
/**
* Converts a GID Buffer to an ID hex string.
* Based off of Spotify.Utils.str2hex(), modified to work with Buffers.
*/
exports.gid2id = function (gid) {
for (var b = '', c = 0, a = gid.length; c < a; ++c) {
b += (gid[c] + 256).toString(16).slice(-2);
}
return b;
};
/**
* ID -> URI
*/
...
fromBytes = function (a, b) { var c = g(a.slice(0).reverse(), 256, 62); return f(d(c, b), "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ").join("") }
n/a
fromHex = function (a, c) { var l = g(f(a, b), 16, 62); return f(d(l, c), "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ").join("") }
...
};
/**
* ID -> URI
*/
exports.id2uri = function (uriType, v) {
var id = base62.fromHex(v, 22);
return 'spotify:' + uriType + ':' + id;
};
/**
* URI -> ID
*
* >>> SpotifyUtil.uri2id('spotify:track:6tdp8sdXrXlPV6AZZN2PE8')
...
toBytes = function (a, b) { var l = g(f(a, c), 62, 256); return d(l, b).reverse() }
n/a
toHex = function (a, b) { var l = g(f(a, c), 62, 16); return f(d(l, b), "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ").join("") }
...
var parts = uri.split(':');
var s;
if (parts.length > 3 && 'playlist' == parts[3]) {
s = parts[4];
} else {
s = parts[2];
}
var v = base62.toHex(s);
return v;
};
/**
* GID -> URI
*/
...
function SpotifyError(err) { this.domain = err[0] || 0; this.code = err[1] || 0; this.description = err[2] || ''; this.data = err[3] || null; // Error impl this.name = domains[this.domain]; var msg = codes[this.code]; if (this.description) msg += ' (' + this.description + ')'; this.message = msg; Error.captureStackTrace(this, SpotifyError); }
n/a
function Error() { [native code] }
n/a
image = function (values) { ProtoBuf.Builder.Message.call(this); var i, field; // Create fields on the object itself to allow setting and getting through Message#fieldname for (i=0; i<fields.length; i++) { field = fields[i]; this[field.name] = (field.repeated) ? [] : null; } // Set the default values for (i=0; i<fields.length; i++) { field = fields[i]; if (typeof field.options['default'] != 'undefined') { try { this.set(field.name, field.options['default']); // Should not throw } catch (e) { throw(new Error("[INTERNAL] "+e)); } } } // Set field values from a values object if (arguments.length == 1 && typeof values == 'object' && /* not another Message */ typeof values.encode != 'function' && /* not a repeated field */ !ProtoBuf.Util.isArray(values) && /* not a ByteBuffer */ !(values instanceof ByteBuffer) && /* not an ArrayBuffer */ !(values instanceof ArrayBuffer) && /* not a Long */ !(ProtoBuf.Long && values instanceof ProtoBuf.Long)) { var keys = Object.keys(values); for (i=0; i<keys.length; i++) { this.set(keys[i], values[keys[i]]); // May throw } // Else set field values from arguments, in correct order } else { for (i=0; i<arguments.length; i++) { if (i<fields.length) { this.set(fields[i].name, arguments[i]); // May throw } } } }
n/a
decode = function (buffer, enc) { if (buffer === null) throw(new Error("buffer must not be null")); if (typeof buffer === 'string') { buffer = ByteBuffer.wrap(buffer, enc ? enc : "base64"); } buffer = buffer instanceof ByteBuffer ? buffer : ByteBuffer.wrap(buffer); // May throw var le = buffer.littleEndian; try { var msg = T.decode(buffer.LE()); buffer.LE(le); return msg; } catch (e) { buffer.LE(le); throw(e); } }
n/a
decode64 = function (str) { return Message.decode(str, "base64"); }
n/a
decodeHex = function (str) { return Message.decode(str, "hex"); }
n/a
function protobufjs_parse_wrapper() { debug('protobufjs_parse_wrapper(%j)', arguments); // Call the message object decode function with the arguments var message = messageObj.decode.apply(null, arguments); // Convert the object keys to camel case, ByteBuffers to Node Buffers and then return the parsed object return convertByteBuffersToNodeBuffers(reCamelCase(message)); }
n/a
function protobufjs_serialize_wrapper() { debug('protobufjs_serialize_wrapper(%j)', arguments); // Convert any camel cased properties in the arguments to underscored properties Array.prototype.map.call(arguments, function (argument) { return deCamelCase(argument); }); // Call the message object constructor with the modified arguments var message = Object.create(messageObj.prototype); message = messageObj.apply(message, arguments) || message; // Return the node Buffer object containing the serialised data return message.encodeNB(); }
n/a
add = function (key, value) { var field = T.getChild(key); if (!field) { throw(new Error(this+"#"+key+" is undefined")); } if (!(field instanceof ProtoBuf.Reflect.Message.Field)) { throw(new Error(this+"#"+key+" is not a field: "+field.toString(true))); // May throw if it's an enum or embedded message } if (!field.repeated) { throw(new Error(this+"#"+key+" is not a repeated field")); } if (this[field.name] === null) this[field.name] = []; this[field.name].push(field.verifyValue(value, true)); }
n/a
encode = function (buffer) { buffer = buffer || new ByteBuffer(); var le = buffer.littleEndian; try { return T.encode(this, buffer.LE()).flip().LE(le); } catch (e) { buffer.LE(le); throw(e); } }
n/a
encode64 = function () { try { return this.encode().toBase64(); } catch (err) { if (err["encoded"]) err["encoded"] = err["encoded"].toBase64(); throw(err); } }
n/a
encodeAB = function () { var enc; try { return this.encode().toArrayBuffer(); } catch (err) { if (err["encoded"]) err["encoded"] = err["encoded"].toArrayBuffer(); throw(err); } }
n/a
encodeHex = function () { try { return this.encode().toHex(); } catch (err) { if (err["encoded"]) err["encoded"] = err["encoded"].toHex(); throw(err); } }
n/a
encodeNB = function () { try { return this.encode().toBuffer(); } catch (err) { if (err["encoded"]) err["encoded"] = err["encoded"].toBuffer(); throw(err); } }
...
});
// Call the message object constructor with the modified arguments
var message = Object.create(messageObj.prototype);
message = messageObj.apply(message, arguments) || message;
// Return the node Buffer object containing the serialised data
return message.encodeNB();
};
}
return messageObj;
};
var deCamelCase = function(obj) {
...
get = function (key) { var field = T.getChild(key); if (!field || !(field instanceof ProtoBuf.Reflect.Message.Field)) { throw(new Error(this+"#"+key+" is not a field: undefined")); } if (!(field instanceof ProtoBuf.Reflect.Message.Field)) { throw(new Error(this+"#"+key+" is not a field: "+field.toString(true))); } return this[field.name]; }
...
var username = process.env.USERNAME;
var password = process.env.PASSWORD;
Spotify.login(username, password, function (err, spotify) {
if (err) throw err;
// first get a "Track" instance from the track URI
spotify.get(uri, function (err, track) {
if (err) throw err;
console.log('Playing: %s - %s', track.artist[0].name, track.name);
// play() returns a readable stream of MP3 audio data
track.play()
.pipe(new lame.Decoder())
.pipe(new Speaker())
...
getFileId = function () { return this.get(field.name); // Does not throw, field exists }
n/a
getHeight = function () { return this.get(field.name); // Does not throw, field exists }
n/a
getSize = function () { return this.get(field.name); // Does not throw, field exists }
n/a
getWidth = function () { return this.get(field.name); // Does not throw, field exists }
n/a
get_file_id = function () { return this.get(field.name); // Does not throw, field exists }
n/a
get_height = function () { return this.get(field.name); // Does not throw, field exists }
n/a
get_size = function () { return this.get(field.name); // Does not throw, field exists }
n/a
get_width = function () { return this.get(field.name); // Does not throw, field exists }
n/a
set = function (key, value) { var field = T.getChild(key); if (!field) { throw(new Error(this+"#"+key+" is not a field: undefined")); } if (!(field instanceof ProtoBuf.Reflect.Message.Field)) { throw(new Error(this+"#"+key+" is not a field: "+field.toString(true))); } this[field.name] = field.verifyValue(value); // May throw }
...
// initiate a "play session" for this Track
spotify.trackUri(track, function (err, res) {
if (err) return stream.emit('error', err);
if (!res.uri) return stream.emit('error', new Error('response contained no "uri"'));
debug('GET %s', res.uri);
track._playSession = res;
var req = spotify.agent.get(res.uri)
.set({ 'User-Agent': spotify.userAgent })
.end()
.request();
req.on('response', response);
});
function response (res) {
debug('HTTP/%s %s', res.httpVersion, res.statusCode);
...
setFileId = function (value) { this.set(field.name, value); }
n/a
setHeight = function (value) { this.set(field.name, value); }
n/a
setSize = function (value) { this.set(field.name, value); }
n/a
setWidth = function (value) { this.set(field.name, value); }
n/a
set_file_id = function (value) { this.set(field.name, value); }
n/a
set_height = function (value) { this.set(field.name, value); }
n/a
set_size = function (value) { this.set(field.name, value); }
n/a
set_width = function (value) { this.set(field.name, value); }
n/a
toArrayBuffer = function () { var enc; try { return this.encode().toArrayBuffer(); } catch (err) { if (err["encoded"]) err["encoded"] = err["encoded"].toArrayBuffer(); throw(err); } }
n/a
toBase64 = function () { try { return this.encode().toBase64(); } catch (err) { if (err["encoded"]) err["encoded"] = err["encoded"].toBase64(); throw(err); } }
n/a
toBuffer = function () { try { return this.encode().toBuffer(); } catch (err) { if (err["encoded"]) err["encoded"] = err["encoded"].toBuffer(); throw(err); } }
...
};
var convertByteBuffersToNodeBuffers = function(obj) {
if (obj === null || 'object' != typeof obj) return obj;
Object.keys(obj).forEach(function(key) {
// attempt to detect a bytebuffer object
if (obj[key] && obj[key].hasOwnProperty('array') && obj[key].hasOwnProperty('view')) {
obj[key] = obj[key].toBuffer();
} else {
obj[key] = convertByteBuffersToNodeBuffers(obj[key]);
}
});
return obj;
};
...
toHex = function () { try { return this.encode().toHex(); } catch (err) { if (err["encoded"]) err["encoded"] = err["encoded"].toHex(); throw(err); } }
...
var parts = uri.split(':');
var s;
if (parts.length > 3 && 'playlist' == parts[3]) {
s = parts[4];
} else {
s = parts[2];
}
var v = base62.toHex(s);
return v;
};
/**
* GID -> URI
*/
...
toString = function () { return T.toString(); }
...
/**
* Converts a GID Buffer to an ID hex string.
* Based off of Spotify.Utils.str2hex(), modified to work with Buffers.
*/
exports.gid2id = function (gid) {
for (var b = '', c = 0, a = gid.length; c < a; ++c) {
b += (gid[c] + 256).toString(16).slice(-2);
}
return b;
};
/**
* ID -> URI
*/
...
restriction = function (values) { ProtoBuf.Builder.Message.call(this); var i, field; // Create fields on the object itself to allow setting and getting through Message#fieldname for (i=0; i<fields.length; i++) { field = fields[i]; this[field.name] = (field.repeated) ? [] : null; } // Set the default values for (i=0; i<fields.length; i++) { field = fields[i]; if (typeof field.options['default'] != 'undefined') { try { this.set(field.name, field.options['default']); // Should not throw } catch (e) { throw(new Error("[INTERNAL] "+e)); } } } // Set field values from a values object if (arguments.length == 1 && typeof values == 'object' && /* not another Message */ typeof values.encode != 'function' && /* not a repeated field */ !ProtoBuf.Util.isArray(values) && /* not a ByteBuffer */ !(values instanceof ByteBuffer) && /* not an ArrayBuffer */ !(values instanceof ArrayBuffer) && /* not a Long */ !(ProtoBuf.Long && values instanceof ProtoBuf.Long)) { var keys = Object.keys(values); for (i=0; i<keys.length; i++) { this.set(keys[i], values[keys[i]]); // May throw } // Else set field values from arguments, in correct order } else { for (i=0; i<arguments.length; i++) { if (i<fields.length) { this.set(fields[i].name, arguments[i]); // May throw } } } }
n/a
decode = function (buffer, enc) { if (buffer === null) throw(new Error("buffer must not be null")); if (typeof buffer === 'string') { buffer = ByteBuffer.wrap(buffer, enc ? enc : "base64"); } buffer = buffer instanceof ByteBuffer ? buffer : ByteBuffer.wrap(buffer); // May throw var le = buffer.littleEndian; try { var msg = T.decode(buffer.LE()); buffer.LE(le); return msg; } catch (e) { buffer.LE(le); throw(e); } }
n/a
decode64 = function (str) { return Message.decode(str, "base64"); }
n/a
decodeHex = function (str) { return Message.decode(str, "hex"); }
n/a
function protobufjs_parse_wrapper() { debug('protobufjs_parse_wrapper(%j)', arguments); // Call the message object decode function with the arguments var message = messageObj.decode.apply(null, arguments); // Convert the object keys to camel case, ByteBuffers to Node Buffers and then return the parsed object return convertByteBuffersToNodeBuffers(reCamelCase(message)); }
n/a
function protobufjs_serialize_wrapper() { debug('protobufjs_serialize_wrapper(%j)', arguments); // Convert any camel cased properties in the arguments to underscored properties Array.prototype.map.call(arguments, function (argument) { return deCamelCase(argument); }); // Call the message object constructor with the modified arguments var message = Object.create(messageObj.prototype); message = messageObj.apply(message, arguments) || message; // Return the node Buffer object containing the serialised data return message.encodeNB(); }
n/a
add = function (key, value) { var field = T.getChild(key); if (!field) { throw(new Error(this+"#"+key+" is undefined")); } if (!(field instanceof ProtoBuf.Reflect.Message.Field)) { throw(new Error(this+"#"+key+" is not a field: "+field.toString(true))); // May throw if it's an enum or embedded message } if (!field.repeated) { throw(new Error(this+"#"+key+" is not a repeated field")); } if (this[field.name] === null) this[field.name] = []; this[field.name].push(field.verifyValue(value, true)); }
n/a
encode = function (buffer) { buffer = buffer || new ByteBuffer(); var le = buffer.littleEndian; try { return T.encode(this, buffer.LE()).flip().LE(le); } catch (e) { buffer.LE(le); throw(e); } }
n/a
encode64 = function () { try { return this.encode().toBase64(); } catch (err) { if (err["encoded"]) err["encoded"] = err["encoded"].toBase64(); throw(err); } }
n/a
encodeAB = function () { var enc; try { return this.encode().toArrayBuffer(); } catch (err) { if (err["encoded"]) err["encoded"] = err["encoded"].toArrayBuffer(); throw(err); } }
n/a
encodeHex = function () { try { return this.encode().toHex(); } catch (err) { if (err["encoded"]) err["encoded"] = err["encoded"].toHex(); throw(err); } }
n/a
encodeNB = function () { try { return this.encode().toBuffer(); } catch (err) { if (err["encoded"]) err["encoded"] = err["encoded"].toBuffer(); throw(err); } }
...
});
// Call the message object constructor with the modified arguments
var message = Object.create(messageObj.prototype);
message = messageObj.apply(message, arguments) || message;
// Return the node Buffer object containing the serialised data
return message.encodeNB();
};
}
return messageObj;
};
var deCamelCase = function(obj) {
...
get = function (key) { var field = T.getChild(key); if (!field || !(field instanceof ProtoBuf.Reflect.Message.Field)) { throw(new Error(this+"#"+key+" is not a field: undefined")); } if (!(field instanceof ProtoBuf.Reflect.Message.Field)) { throw(new Error(this+"#"+key+" is not a field: "+field.toString(true))); } return this[field.name]; }
...
var username = process.env.USERNAME;
var password = process.env.PASSWORD;
Spotify.login(username, password, function (err, spotify) {
if (err) throw err;
// first get a "Track" instance from the track URI
spotify.get(uri, function (err, track) {
if (err) throw err;
console.log('Playing: %s - %s', track.artist[0].name, track.name);
// play() returns a readable stream of MP3 audio data
track.play()
.pipe(new lame.Decoder())
.pipe(new Speaker())
...
getCatalogue = function () { return this.get(field.name); // Does not throw, field exists }
n/a
getCountriesAllowed = function () { return this.get(field.name); // Does not throw, field exists }
n/a
getCountriesForbidden = function () { return this.get(field.name); // Does not throw, field exists }
n/a
getType = function () { return this.get(field.name); // Does not throw, field exists }
n/a
get_catalogue = function () { return this.get(field.name); // Does not throw, field exists }
n/a
get_countries_allowed = function () { return this.get(field.name); // Does not throw, field exists }
n/a
get_countries_forbidden = function () { return this.get(field.name); // Does not throw, field exists }
n/a
get_type = function () { return this.get(field.name); // Does not throw, field exists }
n/a
set = function (key, value) { var field = T.getChild(key); if (!field) { throw(new Error(this+"#"+key+" is not a field: undefined")); } if (!(field instanceof ProtoBuf.Reflect.Message.Field)) { throw(new Error(this+"#"+key+" is not a field: "+field.toString(true))); } this[field.name] = field.verifyValue(value); // May throw }
...
// initiate a "play session" for this Track
spotify.trackUri(track, function (err, res) {
if (err) return stream.emit('error', err);
if (!res.uri) return stream.emit('error', new Error('response contained no "uri"'));
debug('GET %s', res.uri);
track._playSession = res;
var req = spotify.agent.get(res.uri)
.set({ 'User-Agent': spotify.userAgent })
.end()
.request();
req.on('response', response);
});
function response (res) {
debug('HTTP/%s %s', res.httpVersion, res.statusCode);
...
setCatalogue = function (value) { this.set(field.name, value); }
n/a
setCountriesAllowed = function (value) { this.set(field.name, value); }
n/a
setCountriesForbidden = function (value) { this.set(field.name, value); }
n/a
setType = function (value) { this.set(field.name, value); }
n/a
set_catalogue = function (value) { this.set(field.name, value); }
n/a
set_countries_allowed = function (value) { this.set(field.name, value); }
n/a
set_countries_forbidden = function (value) { this.set(field.name, value); }
n/a
set_type = function (value) { this.set(field.name, value); }
n/a
toArrayBuffer = function () { var enc; try { return this.encode().toArrayBuffer(); } catch (err) { if (err["encoded"]) err["encoded"] = err["encoded"].toArrayBuffer(); throw(err); } }
n/a
toBase64 = function () { try { return this.encode().toBase64(); } catch (err) { if (err["encoded"]) err["encoded"] = err["encoded"].toBase64(); throw(err); } }
n/a
toBuffer = function () { try { return this.encode().toBuffer(); } catch (err) { if (err["encoded"]) err["encoded"] = err["encoded"].toBuffer(); throw(err); } }
...
};
var convertByteBuffersToNodeBuffers = function(obj) {
if (obj === null || 'object' != typeof obj) return obj;
Object.keys(obj).forEach(function(key) {
// attempt to detect a bytebuffer object
if (obj[key] && obj[key].hasOwnProperty('array') && obj[key].hasOwnProperty('view')) {
obj[key] = obj[key].toBuffer();
} else {
obj[key] = convertByteBuffersToNodeBuffers(obj[key]);
}
});
return obj;
};
...
toHex = function () { try { return this.encode().toHex(); } catch (err) { if (err["encoded"]) err["encoded"] = err["encoded"].toHex(); throw(err); } }
...
var parts = uri.split(':');
var s;
if (parts.length > 3 && 'playlist' == parts[3]) {
s = parts[4];
} else {
s = parts[2];
}
var v = base62.toHex(s);
return v;
};
/**
* GID -> URI
*/
...
toString = function () { return T.toString(); }
...
/**
* Converts a GID Buffer to an ID hex string.
* Based off of Spotify.Utils.str2hex(), modified to work with Buffers.
*/
exports.gid2id = function (gid) {
for (var b = '', c = 0, a = gid.length; c < a; ++c) {
b += (gid[c] + 256).toString(16).slice(-2);
}
return b;
};
/**
* ID -> URI
*/
...
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]; if (messageObj) break; } } else { // protobufjs // Load the message directly messageObj = packageObj[messageId]; // Add wrapper functions messageObj.parse = function protobufjs_parse_wrapper() { debug('protobufjs_parse_wrapper(%j)', arguments); // Call the message object decode function with the arguments var message = messageObj.decode.apply(null, arguments); // Convert the object keys to camel case, ByteBuffers to Node Buffers and then return the parsed object return convertByteBuffersToNodeBuffers(reCamelCase(message)); } messageObj.serialize = function protobufjs_serialize_wrapper() { debug('protobufjs_serialize_wrapper(%j)', arguments); // Convert any camel cased properties in the arguments to underscored properties Array.prototype.map.call(arguments, function (argument) { return deCamelCase(argument); }); // Call the message object constructor with the modified arguments var message = Object.create(messageObj.prototype); message = messageObj.apply(message, arguments) || message; // Return the node Buffer object containing the serialised data return message.encodeNB(); }; } return messageObj; }
...
/**
* Module dependencies.
*/
var util = require('./util');
var Album = require('./schemas').build('metadata', 'Album'
;);
var debug = require('debug')('spotify-web:album');
/**
* Module exports.
*/
exports = module.exports = Album;
...
function Spotify() { if (!(this instanceof Spotify)) return new Spotify(); EventEmitter.call(this); this.seq = 0; this.heartbeatInterval = 18E4; // 180s, from "spotify.web.client.js" this.agent = superagent.agent(); this.connected = false; // true after the WebSocket "connect" message is sent this._callbacks = Object.create(null); this.authServer = 'play.spotify.com'; this.authUrl = '/xhr/json/auth.php'; this.landingUrl = '/'; this.userAgent = 'Mozilla/5.0 (Chrome/13.37 compatible-ish) spotify-web/' + pkg.version; // base URLs for Image files like album artwork, artist prfiles, etc. // these values taken from "spotify.web.client.js" this.sourceUrl = 'https://d3rt1990lpmkn.cloudfront.net'; this.sourceUrls = { tiny: this.sourceUrl + '/60/', small: this.sourceUrl + '/120/', normal: this.sourceUrl + '/300/', large: this.sourceUrl + '/640/', avatar: this.sourceUrl + '/artist_image/' }; // mappings for the protobuf `enum Size` this.sourceUrls.DEFAULT = this.sourceUrls.normal; this.sourceUrls.SMALL = this.sourceUrls.tiny; this.sourceUrls.LARGE = this.sourceUrls.large; this.sourceUrls.XLARGE = this.sourceUrls.avatar; // WebSocket callbacks this._onopen = this._onopen.bind(this); this._onclose = this._onclose.bind(this); this._onmessage = this._onmessage.bind(this); // start the "heartbeat" once the WebSocket connection is established this.once('connect', this._startHeartbeat); // handle "message" commands... this.on('message', this._onmessagecommand); // needs to emulate Spotify's "CodeValidator" object this._context = vm.createContext(); this._context.reply = this._reply.bind(this); // binded callback for when user doesn't pass a callback function this._defaultCallback = this._defaultCallback.bind(this); }
n/a
gid2id = function (gid) { for (var b = '', c = 0, a = gid.length; c < a; ++c) { b += (gid[c] + 256).toString(16).slice(-2); } return b; }
...
/**
* Track Preview URL getter
*/
Object.defineProperty(Track.prototype, 'previewUrl', {
get: function () {
var previewUrlBase = 'http://d318706lgtcm8e.cloudfront.net/mp3-preview/'
return this.preview.length && (previewUrlBase + util.gid2id(this.preview
[0].fileId));
},
enumerable: true,
configurable: true
})
/**
* Loads all the metadata for this Track instance. Useful for when you get an only
...
gid2uri = function (uriType, gid) { var id = exports.gid2id(gid); return exports.id2uri(uriType, id); }
...
/**
* Album URI getter.
*/
Object.defineProperty(Album.prototype, 'uri', {
get: function () {
return util.gid2uri('album', this.gid);
},
enumerable: true,
configurable: true
});
/**
* Loads all the metadata for this Album instance. Useful for when you get an only
...
id2uri = function (uriType, v) { var id = base62.fromHex(v, 22); return 'spotify:' + uriType + ':' + id; }
...
/**
* GID -> URI
*/
exports.gid2uri = function (uriType, gid) {
var id = exports.gid2id(gid);
return exports.id2uri(uriType, id);
};
/**
* Accepts a String URI, returns the "type" of URI.
* i.e. one of "local", "playlist", "track", etc.
*/
...
login = function (un, pw, fn) { if (!fn) fn = function () {}; var spotify = new Spotify(); spotify.login(un, pw, function (err) { if (err) return fn(err); fn.call(spotify, null, spotify); }); return spotify; }
...
var Spotify = require('spotify-web');
var uri = process.argv[2] || 'spotify:track:6tdp8sdXrXlPV6AZZN2PE8';
// Spotify credentials...
var username = process.env.USERNAME;
var password = process.env.PASSWORD;
Spotify.login(username, password, function (err, spotify) {
if (err) throw err;
// first get a "Track" instance from the track URI
spotify.get(uri, function (err, track) {
if (err) throw err;
console.log('Playing: %s - %s', track.artist[0].name, track.name);
...
function EventEmitter() { EventEmitter.init.call(this); }
n/a
uri2id = function (uri) { var parts = uri.split(':'); var s; if (parts.length > 3 && 'playlist' == parts[3]) { s = parts[4]; } else { s = parts[2]; } var v = base62.toHex(s); return v; }
...
var id = base62.fromHex(v, 22);
return 'spotify:' + uriType + ':' + id;
};
/**
* URI -> ID
*
* >>> SpotifyUtil.uri2id('spotify:track:6tdp8sdXrXlPV6AZZN2PE8
x27;)
* 'd49fcea60d1f450691669b67af3bda24'
* >>> SpotifyUtil.uri2id('spotify:user:tootallnate:playlist:0Lt5S4hGarhtZmtz7BNTeX')
* '192803a20370c0995f271891a32da6a3'
*/
exports.uri2id = function (uri) {
var parts = uri.split(':');
...
uriType = function (uri) { var parts = uri.split(':'); var len = parts.length; if (len >= 3 && 'local' == parts[1]) { return 'local'; } else if (len >= 5) { return parts[3]; } else if (len >= 4 && 'starred' == parts[3]) { return 'playlist'; } else if (len >= 3) { return parts[1]; } else { throw new Error('could not determine "type" for URI: ' + uri); } }
n/a
_defaultCallback = function (err) { if (err) this.emit('error', err); }
n/a
_makeLandingPageRequest = function () { var url = 'https://' + this.authServer + this.landingUrl; debug('GET %j', url); this.agent.get(url) .set({ 'User-Agent': this.userAgent }) .end(this._onsecret.bind(this)); }
n/a
_onauth = function (err, res) { if (err) return this.emit('error', err); debug('auth %d status code, %j content-type', res.statusCode, res.headers['content-type']); if ('ERROR' == res.body.status) { // got an error... var msg = res.body.error; if (res.body.message) msg += ': ' + res.body.message; this.emit('error', new Error(msg)); } else { this.settings = res.body.config; this._resolveAP(); } }
n/a
_onclose = function () { debug('WebSocket "close" event'); this.emit('close'); if (this.connected) { this.disconnect(); } }
n/a
_onconnect = function (err, res) { if (err) return this.emit('error', err); if ('ok' == res.result) { this.connected = true; this.emit('connect'); } else { // TODO: handle possible error case } }
n/a
_onheartbeat = function () { this.sendCommand('sp/echo', 'h'); }
n/a
_onmessage = function (data) { debug('WebSocket "message" event: %s', data); var msg; try { msg = JSON.parse(data); } catch (e) { return this.emit('error', e); } var self = this; var id = msg.id; var callbacks = this._callbacks; function fn (err, res) { var cb = callbacks[id]; if (cb) { // got a callback function! delete callbacks[id]; cb.call(self, err, res, msg); } } if ('error' in msg) { var err = new SpotifyError(msg.error); if (null == id) { this.emit('error', err); } else { fn(err); } } else if ('message' in msg) { var command = msg.message[0]; var args = msg.message.slice(1); this.emit('message', command, args); } else if ('id' in msg) { fn(null, msg); } else { // unhandled command console.error(msg); throw new Error('TODO: implement!'); } }
n/a
_onmessagecommand = function (command, args) { if ('do_work' == command) { var js = args[0]; debug('got "do_work" payload: %j', js); try { vm.runInContext(js, this._context); } catch (e) { this.emit('error', e); } } else if ('ping_flash2' == command) { this.sendPong(args[0]); } else if ('login_complete' == command) { this.sendCommand('sp/log', [41, 1, 0, 0, 0, 0]); // Spotify.Logging.Logger#logWindowSize this.sendCommand('sp/user_info', this._onuserinfo.bind(this)); } else { // unhandled message console.error(command, args); throw new Error('TODO: implement!'); } }
n/a
_onopen = function () { debug('WebSocket "open" event'); this.emit('open'); if (!this.connected) { // need to send "connect" message this.connect(); } }
n/a
_onsecret = function (err, res) { if (err) return this.emit('error', err); debug('landing page: %d status code, %j content-type', res.statusCode, res.headers['content-type']); var $ = cheerio.load(res.text); // need to grab the CSRF token and trackingId from the page. // currently, it's inside an Object that gets passed to a // `new Spotify.Web.Login()` call as the second parameter. var args; var scripts = $('script'); function login (doc, data) { debug('Spotify.Web.Login()'); args = data; return { init: function () { /* noop */ } }; } for (var i = 0; i < scripts.length; i++) { var code = scripts.eq(i).text(); if (~code.indexOf('Spotify.Web.Login')) { vm.runInNewContext(code, { document: null, Spotify: { Web: { Login: login, App: { initialize: function() { } } } } }); } } debug('login CSRF token: %j, tracking ID: %j', args.csrftoken, args.trackingId); // construct credentials object to send from stored credentials var creds = this.creds; delete this.creds; creds.secret = args.csrftoken; creds.trackingId = args.trackingId; creds.landingURL = args.landingURL; creds.referrer = args.referrer; creds.cf = null; // now we have to "auth" in order to get Spotify Web "credentials" var url = 'https://' + this.authServer + this.authUrl; debug('POST %j', url); this.agent.post(url) .set({ 'User-Agent': this.userAgent }) .type('form') .send(creds) .end(this._onauth.bind(this)); }
n/a
_onuserinfo = function (err, res) { if (err) return this.emit('error', err); this.username = res.result.user; this.country = res.result.country; this.accountType = res.result.catalogue; this.emit('login'); }
n/a
_onworkdone = function (err, res) { if (err) return this.emit('error', err); debug('"sp/work_done" ACK'); }
n/a
_openWebsocket = function (err, res) { if (err) return this.emit('error', err); debug('ap resolver %d status code, %j content-type', res.statusCode, res.headers['content-type']); var ap_list = res.body.ap_list; var url = 'wss://' + ap_list[0] + '/'; debug('WS %j', url); this.ws = new WebSocket(url, null, {"origin": "https://play.spotify.com", "headers":{"User-Agent": this.userAgent}}); this.ws.on('open', this._onopen); this.ws.on('close', this._onclose); this.ws.on('message', this._onmessage); }
n/a
_parse = function (parser, data) { var obj = parser.parse(data); tag(this, obj); return obj; }
n/a
_reply = function () { var args = Array.prototype.slice.call(arguments); debug('reply(%j)', args); this.sendCommand('sp/work_done', args, this._onworkdone); }
n/a
_resolveAP = function () { var query = { client: '24:0:0:' + this.settings.version }; var resolver = this.settings.aps.resolver; debug('ap resolver %j', resolver); if (resolver.site) query.site = resolver.site; // connect to the AP resolver endpoint in order to determine // the WebSocket server URL to connect to next var url = 'http://' + resolver.hostname; debug('GET %j', url); this.agent.get(url) .set({ 'User-Agent': this.userAgent }) .query(query) .end(this._openWebsocket.bind(this)); }
n/a
_setLoginCallbacks = function (fn) { var self = this; function onLogin () { cleanup(); fn(); } function onError (err) { cleanup(); fn(err); } function cleanup () { self.removeListener('login', onLogin); self.removeListener('error', onError); } if ('function' == typeof fn) { this.on('login', onLogin); this.on('error', onError); } }
n/a
_startHeartbeat = function () { debug('starting heartbeat every %s seconds', this.heartbeatInterval / 1000); var fn = this._onheartbeat.bind(this); this._heartbeatId = setInterval(fn, this.heartbeatInterval); }
n/a
anonymousLogin = function (fn) { debug('Spotify#anonymousLogin()'); // save credentials for later... this.creds = { type: 'anonymous' }; this._setLoginCallbacks(fn); this._makeLandingPageRequest(); }
n/a
connect = function (fn) { debug('connect()'); var creds = this.settings.credentials[0].split(':'); var args = [ creds[0], creds[1], creds.slice(2).join(':') ]; this.sendCommand('connect', args, this._onconnect.bind(this)); }
n/a
disconnect = function () { debug('disconnect()'); this.connected = false; clearInterval(this._heartbeatId); this._heartbeatId = null; if (this.ws) { this.ws.close(); this.ws = null; } }
...
console.log('Playing: %s - %s', track.artist[0].name, track.name);
// play() returns a readable stream of MP3 audio data
track.play()
.pipe(new lame.Decoder())
.pipe(new Speaker())
.on('finish', function () {
spotify.disconnect();
});
});
});
```
See the `example` directory for some more example code.
...
facebookLogin = function (fbuid, token, fn) { debug('Spotify#facebookLogin(%j, %j)', fbuid, token); // save credentials for later... this.creds = { fbuid: fbuid, token: token, type: 'fb' }; this._setLoginCallbacks(fn); this._makeLandingPageRequest(); }
n/a
get = function (uris, fn) { debug('metadata(%j)', uris); if (!Array.isArray(uris)) { uris = [ uris ]; } // array of "request" Objects that will be protobuf'd var requests = []; var mtype = ''; uris.forEach(function (uri) { var type = util.uriType(uri); if ('local' == type) { debug('ignoring "local" track URI: %j', uri); return; } var id = util.uri2id(uri); mtype = type; requests.push({ method: 'GET', uri: 'hm://metadata/' + type + '/' + id }); }); var header = { method: 'GET', uri: 'hm://metadata/' + mtype + 's' }; var multiGet = true; if (requests.length == 1) { header = requests[0]; requests = null; multiGet = false; } this.sendProtobufRequest({ header: header, payload: requests, isMultiGet: multiGet, responseSchema: { 'vnd.spotify/metadata-artist': Artist, 'vnd.spotify/metadata-album': Album, 'vnd.spotify/metadata-track': Track } }, function(err, item) { if (err) return fn(err); item._loaded = true; fn(null, item); }); }
...
var username = process.env.USERNAME;
var password = process.env.PASSWORD;
Spotify.login(username, password, function (err, spotify) {
if (err) throw err;
// first get a "Track" instance from the track URI
spotify.get(uri, function (err, track) {
if (err) throw err;
console.log('Playing: %s - %s', track.artist[0].name, track.name);
// play() returns a readable stream of MP3 audio data
track.play()
.pipe(new lame.Decoder())
.pipe(new Speaker())
...
isTrackAvailable = function (track, country) { if (!country) country = this.country; debug('isTrackAvailable()'); var allowed = []; var forbidden = []; var available = false; var restriction; if (Array.isArray(track.restriction)) { for (var i = 0; i < track.restriction.length; i++) { restriction = track.restriction[i]; allowed.push.apply(allowed, restriction.allowed); forbidden.push.apply(forbidden, restriction.forbidden); var isAllowed = !restriction.hasOwnProperty('countriesAllowed') || has(allowed, country); var isForbidden = has(forbidden, country) && forbidden.length > 0; // guessing at names here, corrections welcome... var accountTypeMap = { premium: 'SUBSCRIPTION', unlimited: 'SUBSCRIPTION', free: 'AD' }; if (has(allowed, country) && has(forbidden, country)) { isAllowed = true; isForbidden = false; } var type = accountTypeMap[this.accountType] || 'AD'; var applicable = has(restriction.catalogue, type); available = isAllowed && !isForbidden && applicable; //debug('restriction: %j', restriction); debug('type: %j', type); debug('allowed: %j', allowed); debug('forbidden: %j', forbidden); debug('isAllowed: %j', isAllowed); debug('isForbidden: %j', isForbidden); debug('applicable: %j', applicable); debug('available: %j', available); if (available) break; } } return available; }
n/a
login = function (un, pw, fn) { debug('Spotify#login(%j, %j)', un, pw.replace(/./g, '*')); // save credentials for later... this.creds = { username: un, password: pw, type: 'sp' }; this._setLoginCallbacks(fn); this._makeLandingPageRequest(); }
...
var Spotify = require('spotify-web');
var uri = process.argv[2] || 'spotify:track:6tdp8sdXrXlPV6AZZN2PE8';
// Spotify credentials...
var username = process.env.USERNAME;
var password = process.env.PASSWORD;
Spotify.login(username, password, function (err, spotify) {
if (err) throw err;
// first get a "Track" instance from the track URI
spotify.get(uri, function (err, track) {
if (err) throw err;
console.log('Playing: %s - %s', track.artist[0].name, track.name);
...
metadata = function (uris, fn) { debug('metadata(%j)', uris); if (!Array.isArray(uris)) { uris = [ uris ]; } // array of "request" Objects that will be protobuf'd var requests = []; var mtype = ''; uris.forEach(function (uri) { var type = util.uriType(uri); if ('local' == type) { debug('ignoring "local" track URI: %j', uri); return; } var id = util.uri2id(uri); mtype = type; requests.push({ method: 'GET', uri: 'hm://metadata/' + type + '/' + id }); }); var header = { method: 'GET', uri: 'hm://metadata/' + mtype + 's' }; var multiGet = true; if (requests.length == 1) { header = requests[0]; requests = null; multiGet = false; } this.sendProtobufRequest({ header: header, payload: requests, isMultiGet: multiGet, responseSchema: { 'vnd.spotify/metadata-artist': Artist, 'vnd.spotify/metadata-album': Album, 'vnd.spotify/metadata-track': Track } }, function(err, item) { if (err) return fn(err); item._loaded = true; fn(null, item); }); }
n/a
playlist = function (uri, from, length, fn) { // argument surgery if ('function' == typeof from) { fn = from; from = length = null; } else if ('function' == typeof length) { fn = length; length = null; } if (null == from) from = 0; if (null == length) length = 100; debug('playlist(%j, %j, %j)', uri, from, length); var self = this; var parts = uri.split(':'); var user = parts[2]; var id = parts[4]; var hm = 'hm://playlist/user/' + user + '/playlist/' + id + '?from=' + from + '&length=' + length; this.sendProtobufRequest({ header: { method: 'GET', uri: hm }, responseSchema: SelectedListContent }, fn); }
n/a
recurseAlternatives = function (track, country, fn) { debug('recurseAlternatives()'); function done () { process.nextTick(function () { fn(null, track); }); } if (this.isTrackAvailable(track, country)) { return done(); } else if (Array.isArray(track.alternative)) { var tracks = track.alternative; for (var i = 0; i < tracks.length; i++) { debug('checking alternative track %j', track.uri); track = tracks[i]; if (this.isTrackAvailable(track, country)) { return done(); } } } // not playable process.nextTick(function () { fn(new Error('Track is not playable in country "' + country + '"')); }); }
n/a
rootlist = function (user, from, length, fn) { // argument surgery if ('function' == typeof user) { fn = user; from = length = user = null; } else if ('function' == typeof from) { fn = from; from = length = null; } else if ('function' == typeof length) { fn = length; length = null; } if (null == user) user = this.username; if (null == from) from = 0; if (null == length) length = 100; debug('rootlist(%j, %j, %j)', user, from, length); var self = this; var hm = 'hm://playlist/user/' + user + '/publishedrootlist?from=' + from + '&length=' + length; this.sendProtobufRequest({ header: { method: 'GET', uri: hm }, responseSchema: SelectedListContent }, fn); }
n/a
search = function (opts, fn) { if ('string' == typeof opts) { opts = { query: opts }; } if (null == opts.maxResults || opts.maxResults > 50) { opts.maxResults = 50; } if (null == opts.type) { opts.type = 'all'; } if (null == opts.offset) { opts.offset = 0; } if (null == opts.query) { throw new Error('must pass a "query" option!'); } var types = { tracks: 1, albums: 2, artists: 4, playlists: 8 }; var type; if ('all' == opts.type) { type = types.tracks | types.albums | types.artists | types.playlists; } else if (Array.isArray(opts.type)) { type = 0; opts.type.forEach(function (t) { if (!types.hasOwnProperty(t)) { throw new Error('unknown search "type": ' + opts.type); } type |= types[t]; }); } else if (opts.type in types) { type = types[opts.type]; } else { throw new Error('unknown search "type": ' + opts.type); } var args = [ opts.query, type, opts.maxResults, opts.offset ]; this.sendCommand('sp/search', args, function (err, res) { if (err) return fn(err); // XML-parsing is left up to the user, since they may want to use libxmljs, // or node-sax, or node-xml2js, or whatever. So leave it up to them... fn(null, res.result); }); }
n/a
sendCommand = function (name, args, fn) { if ('function' == typeof args) { fn = args; args = []; } debug('sendCommand(%j, %j)', name, args); var msg = { name: name, id: String(this.seq++), args: args || [] }; if ('function' == typeof fn) { // store callback function for later debug('storing callback function for message id %s', msg.id); this._callbacks[msg.id] = fn; } var data = JSON.stringify(msg); debug('sending command: %s', data); try { this.ws.send(data); } catch (e) { this.emit('error', e); } }
n/a
sendPong = function (ping) { this.agent .get('http://ping-pong.spotify.nodestuff.net/' + ping.split(" ").join("-")) .set({ 'User-Agent': this.userAgent }) .type("json") .end(function (err, res) { if (err) return this.emit('error', err); if (res.body.status == 100) { pong = res.body.pong.split("-").join(" "); this.sendCommand('sp/pong_flash2', [ pong ]); } }.bind(this)); }
n/a
sendProtobufRequest = function (req, fn) { debug('sendProtobufRequest(%j)', req); // extract request object var isMultiGet = req.isMultiGet || false; var payload = req.payload || []; var header = { uri: '', method: '', source: '', contentType: isMultiGet ? 'vnd.spotify/mercury-mget-request' : '' }; if (req.header) { header.uri = req.header.uri || ''; header.method = req.header.method || ''; header.source = req.header.source || ''; } // load payload and response schemas var loadSchema = function(schema, dontRecurse) { if ('string' === typeof schema) { var schemaName = schema.split("#"); schema = schemas.build(schemaName[0], schemaName[1]); if (!schema) throw new Error('Could not load schema: ' + schemaName.join('#')); } else if (schema && !dontRecurse && (!schema.hasOwnProperty('parse') && !schema.hasOwnProperty('serialize'))) { var keys = Object.keys(schema); keys.forEach(function(key) { schema[key] = loadSchema(schema[key], true); }); } return schema; }; var payloadSchema = isMultiGet ? MercuryMultiGetRequest : loadSchema(req.payloadSchema); var responseSchema = loadSchema(req.responseSchema); var isMultiResponseSchema = (!responseSchema.hasOwnProperty('parse')); var parseData = function(type, data, dontRecurse) { var parser = responseSchema; var ret; if (!dontRecurse && 'vnd.spotify/mercury-mget-reply' == type) { ret = []; var response = self._parse(MercuryMultiGetReply, data); response.reply.forEach(function(reply) { var data = parseData(reply.contentType, new Buffer(reply.body, 'base64'), true); ret.push(data); }); debug('parsed multi-get response - %d items', ret.length); } else { if (isMultiResponseSchema) { if (responseSchema.hasOwnProperty(type)) { parser = responseSchema[type]; } else { throw new Error('Unrecognised metadata type: ' + type); } } ret = self._parse(parser, data); debug('parsed response: [ %j ] %j', type, ret); } return ret; }; function getNumber (method) { switch(method) { case "SUB": return 1; case "UNSUB": return 2; default: return 0; } } // construct request var args = [ getNumber(header.method) ]; var data = MercuryRequest.serialize(header).toString('base64'); args.push(data); if (isMultiGet) { if (Array.isArray(req.payload)) { req.payload = {request: req.payload}; } else if (!req.payload.request) { throw new Error('Invalid payload for Multi-Get Request.'); } } if (payload && payloadSchema) { data = payloadSchema.serialize(req.payload).toString('base64'); args.push(data); } // send request and parse response, pass data back to callback var self = this; this.sendCommand('sp/hm_b64', args, function (err, res) { if ('function' !== typeof fn) return; // give up if no callback if (err) return fn(err); var header = self._parse(MercuryRequest, new Buffer(res.result[0], 'base64')); debug('response header: %j', header); // TODO: proper error handling, handle 300 errors var message; if (header.statusCode >= 400 && header.statusCode < 500) { message = header.statusMessage || http.STATUS_CODES[header.statusCode] || 'Unknown Error'; return fn(new Error('Client Error: ' + message + ' (' + header.statusCode + ')')); } if (header.statusCode >= 500 && header.statusCode < 600) { message = header.statusMessage || http.STATUS_CODES[header.statusCode] || 'Unknown Error'; return fn(new Error('Server Error: ' + message + ' (' + header.statusCode + ')')); } if (isMultiGet && 'vnd.spotify/mercury-mget-reply' !== header.contentType) return fn(new Error('Server Error: Server didn\'t send a multi-GET reply for a multi-GET request!')); var data = parseData(header.contentType, new Buffer(res.result[1], 'base64')); fn(null, data); }); }
n/a
sendTrackEnd = function (lid, uri, ms, fn) { debug('sendTrackEnd(%j, %j, %j)', lid, uri, ms); if (!fn) fn = this._defaultCallback; var ms_played = Number(ms); var ms_played_union = ms_played; var n_seeks_forward = 0; var n_seeks_backward = 0; var ms_seeks_forward = 0; var ms_seeks_backward = 0; var ms_latency = 100; var display_track = null; var play_context = 'unknown'; var source_start = 'unknown'; var source_end = 'unknown'; var reason_start = 'unknown'; var reason_end = 'unknown'; var referrer = 'unknown'; var referrer_version = '0.1.0'; var referrer_vendor = 'com.spotify'; var max_continuous = ms_played; var args = [ lid, ms_played, ms_played_union, n_seeks_forward, n_seeks_backward, ms_seeks_forward, ms_seeks_backward, ms_latency, display_track, play_context, source_start, source_end, reason_start, reason_end, referrer, referrer_version, referrer_vendor, max_continuous ]; this.sendCommand('sp/track_end', args, function (err, res) { if (err) return fn(err); if (null == res.result) { // apparently no result means "ok" fn(); } else { // TODO: handle error case } }); }
...
// TODO: add formatting options once we figure that out
var spotify = this._spotify;
var stream = new PassThrough();
// if a song was playing before this, the "track_end" command needs to be sent
var track = spotify.currentTrack;
if (track && track._playSession) {
spotify.sendTrackEnd(track._playSession.lid, track.uri, track.duration);
track._playSession = null;
}
// set this Track instance as the "currentTrack"
spotify.currentTrack = track = this;
// initiate a "play session" for this Track
...
sendTrackEvent = function (lid, event, ms, fn) { debug('sendTrackEvent(%j, %j, %j)', lid, event, ms); var num = event; var args = [ lid, num, ms ]; this.sendCommand('sp/track_event', args, function (err, res) { if (err) return fn(err); console.log(res); }); }
n/a
sendTrackProgress = function (lid, ms, fn) { debug('sendTrackProgress(%j, %j)', lid, ms); var ms_played = Number(ms); var source_start = 'unknown'; var reason_start = 'unknown'; var ms_latency = 100; var play_context = 'unknown'; var display_track = ''; var referrer = 'unknown'; var referrer_version = '0.1.0'; var referrer_vendor = 'com.spotify'; var args = [ lid, source_start, reason_start, ms_played, ms_latency, play_context, display_track, referrer, referrer_version, referrer_vendor ]; this.sendCommand('sp/track_progress', args, function (err, res) { if (err) return fn(err); console.log(res); }); }
n/a
similar = function (uri, fn) { debug('similar(%j)', uri); var parts = uri.split(':'); var type = parts[1]; var id = parts[2]; if (!type || !id || 'track' != type) throw new Error('uri must be a track uri'); this.sendProtobufRequest({ header: { method: 'GET', uri: 'hm://similarity/suggest/' + id }, payload: { country: this.country || 'US', language: this.settings.locale.current || 'en', device: 'web' }, payloadSchema: StoryRequest, responseSchema: StoryList }, fn); }
n/a
starred = function (user, from, length, fn) { // argument surgery if ('function' == typeof from) { fn = from; from = length = null; } else if ('function' == typeof length) { fn = length; length = null; } if (null == from) from = 0; if (null == length) length = 100; debug('starred(%j, %j, %j)', user, from, length); var self = this; var hm = 'hm://playlist/user/' + user + '/starred?from=' + from + '&length=' + length; this.sendProtobufRequest({ header: { method: 'GET', uri: hm }, responseSchema: SelectedListContent }, fn); }
n/a
trackUri = function (track, fn) { debug('trackUri()'); // TODO: make "format" configurable here this.recurseAlternatives(track, this.country, function (err, track) { if (err) return fn(err); var args = [ 'mp3160', util.gid2id(track.gid) ]; debug('sp/track_uri args: %j', args); this.sendCommand('sp/track_uri', args, function (err, res) { if (err) return fn(err); fn(null, res.result); }); }.bind(this)); }
...
track._playSession = null;
}
// set this Track instance as the "currentTrack"
spotify.currentTrack = track = this;
// initiate a "play session" for this Track
spotify.trackUri(track, function (err, res) {
if (err) return stream.emit('error', err);
if (!res.uri) return stream.emit('error', new Error('response contained no "uri"'));
debug('GET %s', res.uri);
track._playSession = res;
var req = spotify.agent.get(res.uri)
.set({ 'User-Agent': spotify.userAgent })
.end()
...
track = function (values) { ProtoBuf.Builder.Message.call(this); var i, field; // Create fields on the object itself to allow setting and getting through Message#fieldname for (i=0; i<fields.length; i++) { field = fields[i]; this[field.name] = (field.repeated) ? [] : null; } // Set the default values for (i=0; i<fields.length; i++) { field = fields[i]; if (typeof field.options['default'] != 'undefined') { try { this.set(field.name, field.options['default']); // Should not throw } catch (e) { throw(new Error("[INTERNAL] "+e)); } } } // Set field values from a values object if (arguments.length == 1 && typeof values == 'object' && /* not another Message */ typeof values.encode != 'function' && /* not a repeated field */ !ProtoBuf.Util.isArray(values) && /* not a ByteBuffer */ !(values instanceof ByteBuffer) && /* not an ArrayBuffer */ !(values instanceof ArrayBuffer) && /* not a Long */ !(ProtoBuf.Long && values instanceof ProtoBuf.Long)) { var keys = Object.keys(values); for (i=0; i<keys.length; i++) { this.set(keys[i], values[keys[i]]); // May throw } // Else set field values from arguments, in correct order } else { for (i=0; i<arguments.length; i++) { if (i<fields.length) { this.set(fields[i].name, arguments[i]); // May throw } } } }
n/a
decode = function (buffer, enc) { if (buffer === null) throw(new Error("buffer must not be null")); if (typeof buffer === 'string') { buffer = ByteBuffer.wrap(buffer, enc ? enc : "base64"); } buffer = buffer instanceof ByteBuffer ? buffer : ByteBuffer.wrap(buffer); // May throw var le = buffer.littleEndian; try { var msg = T.decode(buffer.LE()); buffer.LE(le); return msg; } catch (e) { buffer.LE(le); throw(e); } }
n/a
decode64 = function (str) { return Message.decode(str, "base64"); }
n/a
decodeHex = function (str) { return Message.decode(str, "hex"); }
n/a
function protobufjs_parse_wrapper() { debug('protobufjs_parse_wrapper(%j)', arguments); // Call the message object decode function with the arguments var message = messageObj.decode.apply(null, arguments); // Convert the object keys to camel case, ByteBuffers to Node Buffers and then return the parsed object return convertByteBuffersToNodeBuffers(reCamelCase(message)); }
n/a
function protobufjs_serialize_wrapper() { debug('protobufjs_serialize_wrapper(%j)', arguments); // Convert any camel cased properties in the arguments to underscored properties Array.prototype.map.call(arguments, function (argument) { return deCamelCase(argument); }); // Call the message object constructor with the modified arguments var message = Object.create(messageObj.prototype); message = messageObj.apply(message, arguments) || message; // Return the node Buffer object containing the serialised data return message.encodeNB(); }
n/a
add = function (key, value) { var field = T.getChild(key); if (!field) { throw(new Error(this+"#"+key+" is undefined")); } if (!(field instanceof ProtoBuf.Reflect.Message.Field)) { throw(new Error(this+"#"+key+" is not a field: "+field.toString(true))); // May throw if it's an enum or embedded message } if (!field.repeated) { throw(new Error(this+"#"+key+" is not a repeated field")); } if (this[field.name] === null) this[field.name] = []; this[field.name].push(field.verifyValue(value, true)); }
n/a
encode = function (buffer) { buffer = buffer || new ByteBuffer(); var le = buffer.littleEndian; try { return T.encode(this, buffer.LE()).flip().LE(le); } catch (e) { buffer.LE(le); throw(e); } }
n/a
encode64 = function () { try { return this.encode().toBase64(); } catch (err) { if (err["encoded"]) err["encoded"] = err["encoded"].toBase64(); throw(err); } }
n/a
encodeAB = function () { var enc; try { return this.encode().toArrayBuffer(); } catch (err) { if (err["encoded"]) err["encoded"] = err["encoded"].toArrayBuffer(); throw(err); } }
n/a
encodeHex = function () { try { return this.encode().toHex(); } catch (err) { if (err["encoded"]) err["encoded"] = err["encoded"].toHex(); throw(err); } }
n/a
encodeNB = function () { try { return this.encode().toBuffer(); } catch (err) { if (err["encoded"]) err["encoded"] = err["encoded"].toBuffer(); throw(err); } }
...
});
// Call the message object constructor with the modified arguments
var message = Object.create(messageObj.prototype);
message = messageObj.apply(message, arguments) || message;
// Return the node Buffer object containing the serialised data
return message.encodeNB();
};
}
return messageObj;
};
var deCamelCase = function(obj) {
...
get = function (fn) { if (this._loaded) { // already been loaded... debug('track already loaded'); return process.nextTick(fn.bind(null, null, this)); } var spotify = this._spotify; var self = this; spotify.get(this.uri, function (err, track) { if (err) return fn(err); // extend this Track instance with the new one's properties Object.keys(track).forEach(function (key) { if (!self.hasOwnProperty(key)) { self[key] = track[key]; } }); fn(null, self); }); }
...
var username = process.env.USERNAME;
var password = process.env.PASSWORD;
Spotify.login(username, password, function (err, spotify) {
if (err) throw err;
// first get a "Track" instance from the track URI
spotify.get(uri, function (err, track) {
if (err) throw err;
console.log('Playing: %s - %s', track.artist[0].name, track.name);
// play() returns a readable stream of MP3 audio data
track.play()
.pipe(new lame.Decoder())
.pipe(new Speaker())
...
getAlbum = function () { return this.get(field.name); // Does not throw, field exists }
n/a
getAlternative = function () { return this.get(field.name); // Does not throw, field exists }
n/a
getArtist = function () { return this.get(field.name); // Does not throw, field exists }
n/a
getDiscNumber = function () { return this.get(field.name); // Does not throw, field exists }
n/a
getDuration = function () { return this.get(field.name); // Does not throw, field exists }
n/a
getExplicit = function () { return this.get(field.name); // Does not throw, field exists }
n/a
getExternalId = function () { return this.get(field.name); // Does not throw, field exists }
n/a
getFile = function () { return this.get(field.name); // Does not throw, field exists }
n/a
getGid = function () { return this.get(field.name); // Does not throw, field exists }
n/a
getName = function () { return this.get(field.name); // Does not throw, field exists }
n/a
getNumber = function () { return this.get(field.name); // Does not throw, field exists }
n/a
getPopularity = function () { return this.get(field.name); // Does not throw, field exists }
n/a
getPreview = function () { return this.get(field.name); // Does not throw, field exists }
n/a
getRestriction = function () { return this.get(field.name); // Does not throw, field exists }
n/a
getSalePeriod = function () { return this.get(field.name); // Does not throw, field exists }
n/a
get_album = function () { return this.get(field.name); // Does not throw, field exists }
n/a
get_alternative = function () { return this.get(field.name); // Does not throw, field exists }
n/a
get_artist = function () { return this.get(field.name); // Does not throw, field exists }
n/a
get_disc_number = function () { return this.get(field.name); // Does not throw, field exists }
n/a
get_duration = function () { return this.get(field.name); // Does not throw, field exists }
n/a
get_explicit = function () { return this.get(field.name); // Does not throw, field exists }
n/a
get_external_id = function () { return this.get(field.name); // Does not throw, field exists }
n/a
get_file = function () { return this.get(field.name); // Does not throw, field exists }
n/a
get_gid = function () { return this.get(field.name); // Does not throw, field exists }
n/a
get_name = function () { return this.get(field.name); // Does not throw, field exists }
n/a
get_number = function () { return this.get(field.name); // Does not throw, field exists }
n/a
get_popularity = function () { return this.get(field.name); // Does not throw, field exists }
n/a
get_preview = function () { return this.get(field.name); // Does not throw, field exists }
n/a
get_restriction = function () { return this.get(field.name); // Does not throw, field exists }
n/a
get_sale_period = function () { return this.get(field.name); // Does not throw, field exists }
n/a
metadata = function (fn) { if (this._loaded) { // already been loaded... debug('track already loaded'); return process.nextTick(fn.bind(null, null, this)); } var spotify = this._spotify; var self = this; spotify.get(this.uri, function (err, track) { if (err) return fn(err); // extend this Track instance with the new one's properties Object.keys(track).forEach(function (key) { if (!self.hasOwnProperty(key)) { self[key] = track[key]; } }); fn(null, self); }); }
n/a
play = function () { // TODO: add formatting options once we figure that out var spotify = this._spotify; var stream = new PassThrough(); // if a song was playing before this, the "track_end" command needs to be sent var track = spotify.currentTrack; if (track && track._playSession) { spotify.sendTrackEnd(track._playSession.lid, track.uri, track.duration); track._playSession = null; } // set this Track instance as the "currentTrack" spotify.currentTrack = track = this; // initiate a "play session" for this Track spotify.trackUri(track, function (err, res) { if (err) return stream.emit('error', err); if (!res.uri) return stream.emit('error', new Error('response contained no "uri"')); debug('GET %s', res.uri); track._playSession = res; var req = spotify.agent.get(res.uri) .set({ 'User-Agent': spotify.userAgent }) .end() .request(); req.on('response', response); }); function response (res) { debug('HTTP/%s %s', res.httpVersion, res.statusCode); if (res.statusCode == 200) { res.pipe(stream); } else { stream.emit('error', new Error('HTTP Status Code ' + res.statusCode)); } } // return stream immediately so it can be .pipe()'d return stream; }
...
// first get a "Track" instance from the track URI
spotify.get(uri, function (err, track) {
if (err) throw err;
console.log('Playing: %s - %s', track.artist[0].name, track.name);
// play() returns a readable stream of MP3 audio data
track.play()
.pipe(new lame.Decoder())
.pipe(new Speaker())
.on('finish', function () {
spotify.disconnect();
});
});
...
playPreview = function () { var spotify = this._spotify; var stream = new PassThrough(); var previewUrl = this.previewUrl; if (!previewUrl) { process.nextTick(function() { stream.emit('error', new Error('Track does not have preview available')); }); return stream; } debug('GET %s', previewUrl); var req = spotify.agent.get(previewUrl) .set({ 'User-Agent': spotify.userAgent }) .end() .request(); req.on('response', response); function response (res) { debug('HTTP/%s %s', res.httpVersion, res.statusCode); if (res.statusCode == 200) { res.pipe(stream); } else { stream.emit('error', new Error('HTTP Status Code ' + res.statusCode)); } } // return stream immediately so it can be .pipe()'d return stream; }
n/a
set = function (key, value) { var field = T.getChild(key); if (!field) { throw(new Error(this+"#"+key+" is not a field: undefined")); } if (!(field instanceof ProtoBuf.Reflect.Message.Field)) { throw(new Error(this+"#"+key+" is not a field: "+field.toString(true))); } this[field.name] = field.verifyValue(value); // May throw }
...
// initiate a "play session" for this Track
spotify.trackUri(track, function (err, res) {
if (err) return stream.emit('error', err);
if (!res.uri) return stream.emit('error', new Error('response contained no "uri"'));
debug('GET %s', res.uri);
track._playSession = res;
var req = spotify.agent.get(res.uri)
.set({ 'User-Agent': spotify.userAgent })
.end()
.request();
req.on('response', response);
});
function response (res) {
debug('HTTP/%s %s', res.httpVersion, res.statusCode);
...
setAlbum = function (value) { this.set(field.name, value); }
n/a
setAlternative = function (value) { this.set(field.name, value); }
n/a
setArtist = function (value) { this.set(field.name, value); }
n/a
setDiscNumber = function (value) { this.set(field.name, value); }
n/a
setDuration = function (value) { this.set(field.name, value); }
n/a
setExplicit = function (value) { this.set(field.name, value); }
n/a
setExternalId = function (value) { this.set(field.name, value); }
n/a
setFile = function (value) { this.set(field.name, value); }
n/a
setGid = function (value) { this.set(field.name, value); }
n/a
setName = function (value) { this.set(field.name, value); }
n/a
setNumber = function (value) { this.set(field.name, value); }
n/a
setPopularity = function (value) { this.set(field.name, value); }
n/a
setPreview = function (value) { this.set(field.name, value); }
n/a
setRestriction = function (value) { this.set(field.name, value); }
n/a
setSalePeriod = function (value) { this.set(field.name, value); }
n/a
set_album = function (value) { this.set(field.name, value); }
n/a
set_alternative = function (value) { this.set(field.name, value); }
n/a
set_artist = function (value) { this.set(field.name, value); }
n/a
set_disc_number = function (value) { this.set(field.name, value); }
n/a
set_duration = function (value) { this.set(field.name, value); }
n/a
set_explicit = function (value) { this.set(field.name, value); }
n/a
set_external_id = function (value) { this.set(field.name, value); }
n/a
set_file = function (value) { this.set(field.name, value); }
n/a
set_gid = function (value) { this.set(field.name, value); }
n/a
set_name = function (value) { this.set(field.name, value); }
n/a
set_number = function (value) { this.set(field.name, value); }
n/a
set_popularity = function (value) { this.set(field.name, value); }
n/a
set_preview = function (value) { this.set(field.name, value); }
n/a
set_restriction = function (value) { this.set(field.name, value); }
n/a
set_sale_period = function (value) { this.set(field.name, value); }
n/a
toArrayBuffer = function () { var enc; try { return this.encode().toArrayBuffer(); } catch (err) { if (err["encoded"]) err["encoded"] = err["encoded"].toArrayBuffer(); throw(err); } }
n/a
toBase64 = function () { try { return this.encode().toBase64(); } catch (err) { if (err["encoded"]) err["encoded"] = err["encoded"].toBase64(); throw(err); } }
n/a
toBuffer = function () { try { return this.encode().toBuffer(); } catch (err) { if (err["encoded"]) err["encoded"] = err["encoded"].toBuffer(); throw(err); } }
...
};
var convertByteBuffersToNodeBuffers = function(obj) {
if (obj === null || 'object' != typeof obj) return obj;
Object.keys(obj).forEach(function(key) {
// attempt to detect a bytebuffer object
if (obj[key] && obj[key].hasOwnProperty('array') && obj[key].hasOwnProperty('view')) {
obj[key] = obj[key].toBuffer();
} else {
obj[key] = convertByteBuffersToNodeBuffers(obj[key]);
}
});
return obj;
};
...
toHex = function () { try { return this.encode().toHex(); } catch (err) { if (err["encoded"]) err["encoded"] = err["encoded"].toHex(); throw(err); } }
...
var parts = uri.split(':');
var s;
if (parts.length > 3 && 'playlist' == parts[3]) {
s = parts[4];
} else {
s = parts[2];
}
var v = base62.toHex(s);
return v;
};
/**
* GID -> URI
*/
...
toString = function () { return T.toString(); }
...
/**
* Converts a GID Buffer to an ID hex string.
* Based off of Spotify.Utils.str2hex(), modified to work with Buffers.
*/
exports.gid2id = function (gid) {
for (var b = '', c = 0, a = gid.length; c < a; ++c) {
b += (gid[c] + 256).toString(16).slice(-2);
}
return b;
};
/**
* ID -> URI
*/
...
gid2id = function (gid) { for (var b = '', c = 0, a = gid.length; c < a; ++c) { b += (gid[c] + 256).toString(16).slice(-2); } return b; }
...
/**
* Track Preview URL getter
*/
Object.defineProperty(Track.prototype, 'previewUrl', {
get: function () {
var previewUrlBase = 'http://d318706lgtcm8e.cloudfront.net/mp3-preview/'
return this.preview.length && (previewUrlBase + util.gid2id(this.preview
[0].fileId));
},
enumerable: true,
configurable: true
})
/**
* Loads all the metadata for this Track instance. Useful for when you get an only
...
gid2uri = function (uriType, gid) { var id = exports.gid2id(gid); return exports.id2uri(uriType, id); }
...
/**
* Album URI getter.
*/
Object.defineProperty(Album.prototype, 'uri', {
get: function () {
return util.gid2uri('album', this.gid);
},
enumerable: true,
configurable: true
});
/**
* Loads all the metadata for this Album instance. Useful for when you get an only
...
id2uri = function (uriType, v) { var id = base62.fromHex(v, 22); return 'spotify:' + uriType + ':' + id; }
...
/**
* GID -> URI
*/
exports.gid2uri = function (uriType, gid) {
var id = exports.gid2id(gid);
return exports.id2uri(uriType, id);
};
/**
* Accepts a String URI, returns the "type" of URI.
* i.e. one of "local", "playlist", "track", etc.
*/
...
uri2id = function (uri) { var parts = uri.split(':'); var s; if (parts.length > 3 && 'playlist' == parts[3]) { s = parts[4]; } else { s = parts[2]; } var v = base62.toHex(s); return v; }
...
var id = base62.fromHex(v, 22);
return 'spotify:' + uriType + ':' + id;
};
/**
* URI -> ID
*
* >>> SpotifyUtil.uri2id('spotify:track:6tdp8sdXrXlPV6AZZN2PE8
x27;)
* 'd49fcea60d1f450691669b67af3bda24'
* >>> SpotifyUtil.uri2id('spotify:user:tootallnate:playlist:0Lt5S4hGarhtZmtz7BNTeX')
* '192803a20370c0995f271891a32da6a3'
*/
exports.uri2id = function (uri) {
var parts = uri.split(':');
...
uriType = function (uri) { var parts = uri.split(':'); var len = parts.length; if (len >= 3 && 'local' == parts[1]) { return 'local'; } else if (len >= 5) { return parts[3]; } else if (len >= 4 && 'starred' == parts[3]) { return 'playlist'; } else if (len >= 3) { return parts[1]; } else { throw new Error('could not determine "type" for URI: ' + uri); } }
n/a