scsync
Version:
SoundCloud incoming tracks downloader.
139 lines (132 loc) • 5.17 kB
JavaScript
var get = require('./get');
var a = require('async');
var parse = require('url').parse;
var makeHeaders = function(referrer) {
return {
'Referrer': referrer,
'Connection': 'keep-alive',
'Cache-Control': 'max-age=0',
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.97 Safari/537.11',
'Accept': '*/*',
'Accept-Encoding': '',
'Accept-Language': 'en-US,en;q=0.8',
'Accept-Charset': 'ISO-8859-1,utf-8;q=0.7,*;q=0.3'
};
};
var clientIdFromJS = function(js, cb) {
var i = js.indexOf('\ndefine("config"');
if(i < 0) {
cb(new Error('javascript source code structure changed'));
} else {
var left = ',g="';
i = js.indexOf(left, i);
if(i < 0) {
cb(new Error('javascript source code structure changed'));
} else {
js = js.substr(i + left.length);
cb(null, js.substr(0, js.indexOf('"')));
}
}
};
var clientIdFromHTML = function(html, cb) {
var i = html.indexOf('https://a2.sndcdn.com/assets/sc-');
if(i < 0) {
cb(new Error('html structure changed'));
} else {
html = html.substr(i);
get(html.substr(0, html.indexOf('">')), makeHeaders('https://soundcloud.com/'), function(err, resp) {
if(err) {
cb(err);
} else {
clientIdFromJS(resp.body, cb);
}
});
}
};
var resolvableId = function(resource, clientId, cb) {
get('https://api.sndcdn.com/resolve?url=https%3A//soundcloud.com/' + resource + '&_status_code_map%5B302%5D=200&_status_format=json&client_id=' + clientId, makeHeaders('https://soundcloud.com/'), function(err, resp) {
if(err) {
cb(err);
} else {
var url = parse(resp.headers.location, true);
cb(null, url.pathname.split('/')[2]);
}
});
};
var trackDownloadURL = function(trackId, clientId, cb) {
cb(null, 'https://api.soundcloud.com/tracks/' + trackId + '/download?client_id=' + clientId);
};
module.exports = {
trackId : resolvableId,
artistId : resolvableId,
artistTracks : function(artistId, clientId, forceStreamed, cb) {
get('http://api.soundcloud.com/users/' + artistId + '/tracks.json?client_id=' + clientId, makeHeaders('https://soundcloud.com'), function(err, resp) {
if(err) {
cb(err);
} else {
resp = JSON.parse(resp.body);
var tracks = [];
for(var i = 0; i < resp.length; i += 1) {
var track = resp[i];
if(track.kind === 'track') {
tracks.push(track);
}
}
a.map(tracks, function(track, cb) {
var cb2 = function(err, url) {
if(err) {
cb(err);
} else {
cb(null, {
artist : track.user.permalink,
title : track.permalink,
date : new Date(track.created_at),
url : url
});
}
};
if(track.downloadable && !forceStreamed) {
cb2(null, 'https://api.soundcloud.com/tracks/' + track.id + '/download?client_id=' + clientId);
} else {
cb2(null, 'https://api.soundcloud.com/tracks/' + track.id + '/stream?client_id=' + clientId);
}
}, cb);
}
});
},
trackDownloadURL : trackDownloadURL,
trackStreamURL : trackDownloadURL,
// use the code below, when trackDownloadURL does no longer fall back to stream url
/*
trackStreamURL : function(trackId, clientId, cb) {
get('https://api.sndcdn.com/i1/tracks/' + trackId + '/streams?client_id=' + clientId, makeHeaders('https://soundcloud.com/'), function(err, resp) {
if(err) {
cb(err);
} else {
resp = JSON.parse(resp.body);
var qualities = Object.keys(resp);
if(qualities.length > 1) {
console.log('WARNING: available more qualities, but using just the first one of: ' + qualities);
}
cb(null, resp[qualities[0]]);
}
});
},
*/
clientId : function(cb) {
cb(null, "b45b1aa10f1ac2941910a7f0d10f8e28");
// hack this, when the hardcoded id no longer works
// curl https://a2.sndcdn.com/assets/sc-431833df.js|grep '^define("config"'
// or
// require(['config'], function(config){console.log(config, config._store.client_id)}), 'go'
/*
get('https://soundcloud.com', makeHeaders(null), function(err, resp) {
if(err) {
cb(err);
} else {
clientIdFromHTML(resp.body, cb);
}
});
*/
}
};