anesidora
Version:
A really simple wrapper around the Pandora JSON API
139 lines (125 loc) • 5.03 kB
JavaScript
var request = require("request");
var _ = require("underscore");
var encryption = require("./encryption");
var Anesidora = (function() {
var Anesidora = function(username, password, partnerInfo) {
if (partnerInfo == null) {
partnerInfo = {
"username": "android",
"password": "AC7IBG09A3DTSYM4R41UJWL07VLN8JI7",
"deviceModel": "android-generic",
"decryptPassword": "R=U!LH$O2B#",
"encryptPassword": "6#26FRL$ZWD"
};
}
this.username = username;
this.password = password;
this.partnerInfo = _.extend(partnerInfo, {"version": "5"});
this.authData = null;
};
Anesidora.ENDPOINT = "://tuner.pandora.com/services/json/";
var endpoint = function(secure) {
return (secure ? "https" : "http") + Anesidora.ENDPOINT;
};
var seconds = function() {
return Date.now() / 1000 | 0;
};
var unwrap = function(callback) {
return function(err, res, body) {
if (err) return callback(err);
var parsed = body;
if (typeof parsed === "string") {
parsed = JSON.parse(body);
}
if (parsed.stat === "fail") {
return callback(new Error(parsed.message + " [" + parsed.code + "]"));
} else if (parsed.stat === "ok") {
return callback(null, parsed.result);
} else {
return callback(new Error("Unknown error"));
}
};
};
var decryptSyncTime = function(password, ciphered) {
return parseInt(encryption.decrypt(password, ciphered).toString("utf8", 4, 14), 10);
};
var partnerLogin = function(partnerInfo, callback) {
request({
"method": "post",
"url": endpoint(true),
"qs": {
"method": "auth.partnerLogin"
},
"body": JSON.stringify(_.omit(partnerInfo, ["decryptPassword", "encryptPassword"]))
}, unwrap(function(err, result) {
if (err) return callback(err);
result.syncTimeOffset = decryptSyncTime(partnerInfo.decryptPassword, result.syncTime) - seconds();
callback(null, result);
}));
};
var userLogin = function(encryptPassword, partnerData, username, password, callback) {
request({
"method": "post",
"url": endpoint(true),
"qs": {
"method": "auth.userLogin",
"auth_token": partnerData.partnerAuthToken,
"partner_id": partnerData.partnerId
},
"body": encryption.encrypt(encryptPassword, JSON.stringify({
"loginType": "user",
"username": username,
"password": password,
"partnerAuthToken": partnerData.partnerAuthToken,
"syncTime": partnerData.syncTimeOffset + seconds()
})).toString("hex").toLowerCase()
}, unwrap(callback));
};
Anesidora.prototype.login = function(callback) {
var that = this;
partnerLogin(that.partnerInfo, function(err, partner) {
if (err) return callback(err);
userLogin(that.partnerInfo.encryptPassword, partner, that.username, that.password, function(err, user) {
if (err) return callback(err);
that.authData = {
"userAuthToken": user.userAuthToken,
"partnerId": partner.partnerId,
"userId": user.userId,
"syncTimeOffset": partner.syncTimeOffset
};
callback(null);
});
});
};
Anesidora.prototype.request = function(method, data, callback) {
var that = this;
if (typeof data === "function" && callback == null) {
callback = data;
data = {};
}
if (that.authData == null) {
return callback(new Error("Not authenticated with Pandora (call login() before request())"));
}
var secure = false;
if (method === "station.getPlaylist") secure = true;
var body = _.extend(data, {
"userAuthToken": that.authData.userAuthToken,
"syncTime": that.authData.syncTimeOffset + seconds()
});
var encryptedBody = encryption.encrypt(that.partnerInfo.encryptPassword, JSON.stringify(body)).toString("hex").toLowerCase();
if (method === "test.checkLicensing") encryptedBody = null;
request({
"method": "post",
"url": endpoint(secure),
"qs": {
"method": method,
"auth_token": that.authData.userAuthToken,
"partner_id": that.authData.partnerId,
"user_id": that.authData.userId
},
"body": encryptedBody
}, unwrap(callback));
};
return Anesidora;
})();
module.exports = Anesidora;