Skip to content

Instantly share code, notes, and snippets.

@aylarov
Last active April 11, 2016 05:33
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save aylarov/ee7f509ca182e8437b2abed9d415f2f1 to your computer and use it in GitHub Desktop.
Save aylarov/ee7f509ca182e8437b2abed9d415f2f1 to your computer and use it in GitHub Desktop.
require(Modules.Player);
require(Modules.Conference);
(function () {
var keys = [{
username: "android",
password: "AC7IBG09A3DTSYM4R41UJWL07VLN8JI7",
deviceId: "android-generic",
decrypt: "R=U!LH$O2B#",
encrypt: "6#26FRL$ZWD"
}, {
username: "iphone",
password: "P2E4FC0EAD3*878N92B2CDp34I0B1@388137C",
deviceId: "IP01",
decrypt: "20zE1E47BE57$51",
encrypt: "721^26xE22776"
}, {
username: "palm",
password: "IUC7IBG09A3JTSYM4N11UJWL07VLH8JP0",
deviceId: "pre",
decrypt: "E#U$MY$O2B=",
encrypt: "%526CBL$ZU3"
}, {
username: "winmo",
password: "ED227E10a628EB0E8Pm825Dw7114AC39",
deviceId: "VERIZON_MOTOQ9C",
decrypt: "7D671jt0C5E5d251",
encrypt: "v93C8C2s12E0EBD"
}];
/**
* Pandora API
* As described at https://6xq.net/pandora-apidoc/
*/
Pandora = function () {
var pandoraSecureURL = "https://tuner.pandora.com/services/json/";
var pandoraURL = "http://tuner.pandora.com/services/json/";
var syncTime, partnerId, partnerAuthToken, userId, userAuthToken, userPPID;
this.userId = function () {
return userId;
};
this.unixTs = function () {
return (Math.floor(Date.now() / 1000));
};
this.calculateSyncTime = function (time) {
syncTime = blowfish.decrypt(time, keys[0].decrypt, {
cipherMode: 0,
outputType: 1
});
syncTime = syncTime.slice(4);
//which we parse into a number
syncTime = parseInt(syncTime, 10);
//and we derive an offset to use in the future
syncTime = this.unixTs() - syncTime;
};
this.httpRequest = function (url, options, secure) {
return new Promise(function (resolve, reject) {
url = (secure === false ? pandoraURL : pandoraSecureURL) + url;
Logger.write("Requesting URL: " + url);
Net.httpRequest(url, function (res) {
if (res.code == 200) {
resolve(res);
} else {
reject(res);
}
}, options);
});
};
/**
* Partner login required before user login
*/
this.partnerLogin = function () {
return new Promise(function (resolve, reject) {
var data = {
username: keys[0].username,
password: keys[0].password,
deviceModel: keys[0].deviceId,
version: "5",
includeUrls: true
};
var opts = new Net.HttpRequestOptions();
opts.method = "POST";
opts.postData = JSON.stringify(data);
opts.headers = [
"User-Agent: vox",
"Content-Type: text/plain"
];
this.httpRequest("?method=auth.partnerLogin", opts).then(function (res) {
try {
var result = JSON.parse(res.text);
//Logger.write(JSON.stringify(res.text, null, 2));
if (result.stat != "ok") reject();
syncTime = result.result.syncTime;
this.calculateSyncTime(syncTime);
Logger.write("offset is " + syncTime);
partnerId = result.result.partnerId;
partnerAuthToken = result.result.partnerAuthToken;
resolve(result.result);
} catch (e) {
reject();
}
}.bind(this)).catch(function (err) {
reject(err);
});
}.bind(this));
};
this.login = function (username, password) {
return new Promise(function (resolve, reject) {
this.partnerLogin().then(function (res) {
var data = {
"loginType": "user",
"username": username,
"password": password,
"partnerAuthToken": partnerAuthToken,
"syncTime": this.unixTs() + syncTime
};
var opts = new Net.HttpRequestOptions();
opts.method = "POST";
opts.postData = blowfish.encrypt(JSON.stringify(data), keys[0].encrypt, {
cipherMode: 0,
outputType: 1
});
opts.headers = [
"User-Agent: vox",
"Content-Type: text/plain"
];
Logger.write("POST DATA: " + opts.postData);
this.httpRequest("?method=auth.userLogin&auth_token=" + encodeURIComponent(partnerAuthToken) + "&partner_id=" + partnerId,
opts)
.then(function (res) {
var result = JSON.parse(res.text);
Logger.write(JSON.stringify(res.text, null, 2));
if (result.stat != "ok") reject(JSON.stringify(result, null, 2));
else {
userId = result.result.userId;
userAuthToken = result.result.userAuthToken;
userPPID = result.result.userPPID;
resolve(result.result);
}
}).catch(function (e) {
Logger.write("http request error: " + JSON.stringify(e));
reject(e);
});
}.bind(this)).catch(function (err) {
reject();
});
}.bind(this));
};
this.callPandoraMethod = function (method, data) {
return new Promise(function (resolve, reject) {
data.userAuthToken = userAuthToken;
data.syncTime = this.unixTs() + syncTime;
var opts = new Net.HttpRequestOptions();
opts.method = "POST";
opts.postData = blowfish.encrypt(JSON.stringify(data), keys[0].encrypt, {
cipherMode: 0,
outputType: 1
});
opts.headers = [
"User-Agent: vox",
"Content-Type: text/plain"
];
this.httpRequest("?method=" + method + "&auth_token=" + encodeURIComponent(userAuthToken) + "&partner_id=" + partnerId + "&user_id=" + userId, opts, false)
.then(function (res) {
try {
var result = JSON.parse(res.text);
//Logger.write(JSON.stringify(res.text, null, 2));
if (result.stat != "ok") reject(result);
else resolve(result.result);
} catch (e) {
reject(e);
}
}.bind(this))
.catch(function (err) {
reject(err);
});
}.bind(this));
};
};
})();
function getPlaylist(stationToken) {
for (var i = 0; i < calls.length; i++) {
calls[i].sendMessage(JSON.stringify({
status: "loading_playlist"
}));
}
p.callPandoraMethod("station.getPlaylist", {
"stationToken": stationToken,
"additionalAudioUrl": "HTTP_128_MP3"
}).then(function (res) {
//Logger.write(JSON.stringify(res, null, 2));
tracks = [];
for (var i = 0; i < res.items.length; i++) {
if (typeof res.items[i].songName != "undefined")
tracks.push({
songName: res.items[i].songName,
artistName: res.items[i].artistName,
albumName: res.items[i].albumName,
trackToken: res.items[i].trackToken,
trackURL: res.items[0].additionalAudioUrl
});
}
trackToken = res.items[0].trackToken;
for (var i = 0; i < calls.length; i++) {
calls[i].sendMessage(JSON.stringify({
status: "playback",
tracks: [ tracks[0] ],
trackToken: trackToken,
stationToken: stationToken
}));
}
//call.startPlayback(res.items[0].additionalAudioUrl);
if (typeof player != "undefined") player.stop();
player = VoxEngine.createURLPlayer(res.items[0].additionalAudioUrl);
player.sendMediaTo(hdconf);
player.addEventListener(PlayerEvents.PlaybackFinished, function (e) {
getPlaylist(stationToken);
});
}).catch(function (err) {
VoxEngine.terminate();
});
}
var call, calls = [],
tracks = [],
trackToken,
p, stations = [],
stationToken, player,
hdconf,
PANDORA_USERNAME = "",
PANDORA_PASSWORD = "";
VoxEngine.addEventListener(AppEvents.CallAlerting, function (e) {
Logger.write("Inbound call");
e.call.addEventListener(CallEvents.Connected, function (e) {
calls.push(e.call);
for (var i = 0; i < calls.length; i++) {
calls[i].sendMessage(JSON.stringify({
status: 'listeners_info',
listenersCount: calls.length
}));
}
if (typeof hdconf == "undefined") {
hdconf = VoxEngine.createConference({
hd_audio: true
});
hdconf.sendMediaTo(e.call);
e.call.sendMessage(JSON.stringify({
status: "auth"
}));
} else {
if (stations.length !== 0) {
e.call.sendMessage(JSON.stringify({
status: "stations_list",
stations: stations,
stationToken: stationToken
}));
}
if (tracks.length !== 0) {
e.call.sendMessage(JSON.stringify({
status: "playback",
tracks: [ tracks[0] ],
trackToken: trackToken,
stationToken: stationToken
}));
}
hdconf.sendMediaTo(e.call);
}
});
e.call.addEventListener(CallEvents.Disconnected, function (e) {
for (var i = 0; i < calls.length; i++) {
calls[i].sendMessage(JSON.stringify({
status: 'listeners_info',
listenersCount: calls.length
}));
}
var index = calls.indexOf(e.call);
if (index > -1) {
calls.splice(index, 1);
}
if (calls.length === 0) VoxEngine.terminate();
});
e.call.addEventListener(CallEvents.MessageReceived, function (e) {
try {
var result = JSON.parse(e.text);
if (result.action == "getStationList") {
p.callPandoraMethod("user.getStationList", {}).then(function (res) {
for (var i = 0; i < res.stations.length; i++) {
if (typeof res.stations[i].stationName != "undefined") stations.push({
stationName: res.stations[i].stationName,
stationToken: res.stations[i].stationToken
});
}
e.call.sendMessage(JSON.stringify({
status: "stations_list",
stations: stations
}));
}).catch(function (err) {
Logger.write("getStationList failed");
Logger.write(JSON.stringify(err, null, 2));
VoxEngine.terminate();
});
} else if (result.action == "login") {
p = new Pandora();
p.login(PANDORA_USERNAME, PANDORA_PASSWORD).then(function (res) {
Logger.write("Authorized successfully. User ID: " + p.userId());
e.call.sendMessage(JSON.stringify({
status: "loading_stations"
}));
}).catch(function (e) {
Logger.write("Login failed");
VoxEngine.terminate();
});
} else if (result.action == "getPlaylist") {
stationToken = result.stationToken;
getPlaylist(result.stationToken);
} else if (result.action == "callUser") {
var outbound_call = VoxEngine.callUser(result.username, "Pandora");
outbound_call.addEventListener(CallEvents.Connected, function (e) {
e.call.sendMessage(JSON.stringify({
status: "user_call_connected"
}));
hdconf.sendMediaTo(outbound_call);
});
outbound_call.addEventListener(CallEvents.Failed, function (e) {
e.call.sendMessage(JSON.stringify({
status: "user_call_failed"
}));
});
outbound_call.addEventListener(CallEvents.Disconnected, function (e) {
e.call.sendMessage(JSON.stringify({
status: "user_call_disconnected"
}));
});
}
} catch (e) {
Logger.write(JSON.stringify(e));
}
});
/*call.addEventListener(CallEvents.PlaybackFinished, function (e) {
getPlaylist(stationToken);
});*/
e.call.answer();
});
VoxEngine.addEventListener(AppEvents.Started, function (e) {
Logger.write("Application started");
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment