Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Support playing ads to avoid Pandora account problems #686

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions src/libpiano/piano.h
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ typedef struct PianoSong {
char *feedbackId;
char *detailUrl;
char *trackToken;
char *adToken;
float fileGain;
unsigned int length; /* song length in seconds */
PianoSongRating_t rating;
Expand Down Expand Up @@ -179,6 +180,8 @@ typedef enum {
PIANO_REQUEST_DELETE_SEED = 22,
PIANO_REQUEST_GET_SETTINGS = 23,
PIANO_REQUEST_CHANGE_SETTINGS = 24,
PIANO_REQUEST_GET_AD_METADATA = 25,
PIANO_REQUEST_REGISTER_AD = 26,
} PianoRequestType_t;

typedef struct PianoRequest {
Expand Down Expand Up @@ -266,6 +269,20 @@ typedef struct {
PianoTristate_t explicitContentFilter;
} PianoRequestDataChangeSettings_t;

typedef struct {
char *token;
PianoSong_t *song;
PianoAudioQuality_t quality;
char **retToken;
size_t retTokenCount;
} PianoRequestDataGetAdMetadata_t;

typedef struct {
char **token;
size_t tokenCount;
PianoStation_t *station;
} PianoRequestDataRegisterAd_t;

/* pandora error code offset */
#define PIANO_RET_OFFSET 1024
typedef enum {
Expand Down
91 changes: 91 additions & 0 deletions src/libpiano/request.c
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ PianoReturn_t PianoRequest (PianoHandle_t *ph, PianoRequest_t *req,
json_object_new_string ("5"));
json_object_object_add (j, "includeUrls",
json_object_new_boolean (true));
json_object_object_add (j, "returnDeviceType",
json_object_new_boolean (true));
json_object_object_add (j, "returnUpdatePromptVersions",
json_object_new_boolean (true));
snprintf (req->urlPath, sizeof (req->urlPath),
PIANO_RPC_PATH "method=auth.partnerLogin");
break;
Expand All @@ -95,6 +99,36 @@ PianoReturn_t PianoRequest (PianoHandle_t *ph, PianoRequest_t *req,
json_object_new_string (ph->partner.authToken));
json_object_object_add (j, "syncTime",
json_object_new_int (timestamp));
json_object_object_add (j, "includePandoraOneInfo",
json_object_new_boolean (true));
json_object_object_add (j, "includeDemographics",
json_object_new_boolean (true));
json_object_object_add (j, "includeAdAttributes",
json_object_new_boolean (true));
json_object_object_add (j, "includeShuffleInsteadOfQuickMix",
json_object_new_boolean (true));
json_object_object_add (j, "returnCollectTrackLifetimeStats",
json_object_new_boolean (true));
json_object_object_add (j, "xplatformAdCapable",
json_object_new_boolean (true));
json_object_object_add (j, "returnUserstate",
json_object_new_boolean (true));
json_object_object_add (j, "includeListeningHours",
json_object_new_boolean (true));
json_object_object_add (j, "includeDailySkipLimit",
json_object_new_boolean (true));
json_object_object_add (j, "includeSkipDelay",
json_object_new_boolean (true));
json_object_object_add (j, "includeAdvertiserAttributes",
json_object_new_boolean (true));
json_object_object_add (j, "includePlaylistAttributes",
json_object_new_boolean (true));
json_object_object_add (j, "includeSkipAttributes",
json_object_new_boolean (true));
json_object_object_add (j, "includeStationExpirationTime",
json_object_new_boolean (true));
json_object_object_add (j, "includeStationDescription",
json_object_new_boolean (true));

CURL * const curl = curl_easy_init ();
urlencAuthToken = curl_easy_escape (curl,
Expand Down Expand Up @@ -135,6 +169,20 @@ PianoReturn_t PianoRequest (PianoHandle_t *ph, PianoRequest_t *req,
json_object_new_string (reqData->station->id));
json_object_object_add (j, "includeTrackLength",
json_object_new_boolean (true));
json_object_object_add (j, "includeAudioToken",
json_object_new_boolean (true));
json_object_object_add (j, "xplatformAdCapable",
json_object_new_boolean (true));
json_object_object_add (j, "includeAudioReceiptUrl",
json_object_new_boolean (true));
json_object_object_add (j, "includeCompetitiveSepIndicator",
json_object_new_boolean (true));
json_object_object_add (j, "includeCompletePlaylist",
json_object_new_boolean (true));
json_object_object_add (j, "includeTrackOptions",
json_object_new_boolean (true));
json_object_object_add (j, "audioAdPodCapable",
json_object_new_boolean (true));

method = "station.getPlaylist";
break;
Expand Down Expand Up @@ -444,6 +492,49 @@ PianoReturn_t PianoRequest (PianoHandle_t *ph, PianoRequest_t *req,
break;
}

case PIANO_REQUEST_GET_AD_METADATA: {
PianoRequestDataGetAdMetadata_t *reqData = req->data;

assert (reqData != NULL);
assert (reqData->token != NULL);

json_object_object_add (j, "adToken",
json_object_new_string (reqData->token));
json_object_object_add (j, "returnAdTrackingTokens",
json_object_new_boolean (true));
json_object_object_add (j, "supportAudioAds",
json_object_new_boolean (true));
json_object_object_add (j, "includeBannerAd",
json_object_new_boolean (true));
json_object_object_add (j, "includeListeningHours",
json_object_new_boolean (true));

method = "ad.getAdMetadata";
break;
}

case PIANO_REQUEST_REGISTER_AD: {
PianoRequestDataRegisterAd_t *reqData = req->data;

assert (reqData != NULL);
assert (reqData->token != NULL);
assert (reqData->tokenCount > 0);
assert (reqData->station != NULL);
assert (reqData->station->id != NULL);

json_object_object_add (j, "stationId",
json_object_new_string (reqData->station->id));
json_object * const token = json_object_new_array ();
for (size_t i = 0; i < reqData->tokenCount; i++) {
json_object_array_add (token,
json_object_new_string (reqData->token[i]));
}
json_object_object_add (j, "adTrackingTokens", token);

method = "ad.registerAd";
break;
}

/* "high-level" wrapper */
case PIANO_REQUEST_RATE_SONG: {
/* love/ban song */
Expand Down
86 changes: 86 additions & 0 deletions src/libpiano/response.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ THE SOFTWARE.

#include "../config.h"

#include <stdio.h>
#include <json.h>
#include <string.h>
#include <assert.h>
Expand Down Expand Up @@ -88,6 +89,39 @@ static void PianoStrpcat (char * restrict dest, const char * restrict src,
*dest = '\0';
}

static bool audioMapSelect (json_object * const map,
const PianoAudioQuality_t quality, PianoSong_t * const song) {
assert (map != NULL);
assert (quality != PIANO_AQ_UNKNOWN);
assert (song != NULL);

/* get audio url based on selected quality */
static const char *qualityMap[] = {"", "lowQuality", "mediumQuality",
"highQuality"};
assert (quality < sizeof (qualityMap)/sizeof (*qualityMap));
static const char *formatMap[] = {"", "aacplus", "mp3"};

json_object * const item =
json_object_object_get (map, qualityMap[quality]);

if (item == NULL) {
/* requested quality is not available */
return false;
}

const char *encoding = json_object_get_string (
json_object_object_get (item, "encoding"));
assert (encoding != NULL);
for (size_t k = 0; k < sizeof (formatMap)/sizeof (*formatMap); k++) {
if (strcmp (formatMap[k], encoding) == 0) {
song->audioFormat = k;
break;
}
}
song->audioUrl = PianoJsonStrdup (item, "audioUrl");
return true;
}

/* parse xml response and update data structures/return new data structure
* @param piano handle
* @param initialized request (expects responseData to be a NUL-terminated
Expand Down Expand Up @@ -264,6 +298,11 @@ PianoReturn_t PianoResponse (PianoHandle_t *ph, PianoRequest_t *req) {
return PIANO_RET_OUT_OF_MEMORY;
}

if (json_object_object_get_ex (s, "adToken", NULL)) {
song->adToken = PianoJsonStrdup (s, "adToken");
playlist = PianoListAppendP (playlist, song);
continue;
}
if (!json_object_object_get_ex (s, "artistName", NULL)) {
free (song);
continue;
Expand Down Expand Up @@ -651,6 +690,53 @@ PianoReturn_t PianoResponse (PianoHandle_t *ph, PianoRequest_t *req) {
}
break;
}

case PIANO_REQUEST_GET_AD_METADATA: {
PianoRequestDataGetAdMetadata_t *reqData = req->data;

assert (reqData != NULL);
assert (reqData->song != NULL);
assert (reqData->quality != PIANO_AQ_UNKNOWN);

json_object *token = json_object_object_get (result,
"adTrackingTokens");
if (token != NULL) {
reqData->retTokenCount = json_object_array_length (token);
reqData->retToken = malloc (reqData->retTokenCount *
sizeof (*reqData->retToken));
for (size_t i = 0; i < reqData->retTokenCount; i++) {
json_object * const t = json_object_array_get_idx (token,
i);
assert (t != NULL);
reqData->retToken[i] = strdup (json_object_get_string (t));
printf ("added tracking token %s\n", reqData->retToken[i]);
}
} else {
reqData->retTokenCount = 0;
reqData->retToken = NULL;
}

PianoSong_t * const song = reqData->song;
json_object * const map = json_object_object_get (result, "audioUrlMap");
if (map != NULL) {
if (!audioMapSelect (map, reqData->quality, song)) {
/* requested quality is not available */
ret = PIANO_RET_QUALITY_UNAVAILABLE;
break;
}
}
song->artist = PianoJsonStrdup (result, "companyName");
song->title = PianoJsonStrdup (result, "title");
song->album = strdup ("");
song->fileGain = json_object_get_double (
json_object_object_get (result, "trackGain"));
break;
}

case PIANO_REQUEST_REGISTER_AD: {
printf ("req->responseData: %s\n", req->responseData);
break;
}
}

cleanup:
Expand Down
38 changes: 38 additions & 0 deletions src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,44 @@ static void BarMainStartPlayback (BarApp_t *app, pthread_t *playerThread) {
const PianoSong_t * const curSong = app->playlist;
assert (curSong != NULL);

/* ads */
PianoReturn_t pRet;
CURLcode wRet;

/* is this an advertising track? */
if (curSong->adToken != NULL) {
PianoRequestDataGetAdMetadata_t adReqData;

memset (&adReqData, 0, sizeof (adReqData));
adReqData.token = curSong->adToken;
adReqData.song = curSong;
adReqData.quality = app->settings.audioQuality;

BarUiMsg (&app->settings, MSG_INFO, "Fetching ads with token %s... ",
adReqData.token);
BarUiPianoCall (app, PIANO_REQUEST_GET_AD_METADATA,
&adReqData, &pRet, &wRet);

/* got token? */
if (adReqData.retTokenCount > 0) {
PianoRequestDataRegisterAd_t regReqData;

regReqData.token = adReqData.retToken;
regReqData.tokenCount = adReqData.retTokenCount;
regReqData.station = app->curStation;

BarUiMsg (&app->settings, MSG_INFO, "Registering ad... ");
BarUiPianoCall (app, PIANO_REQUEST_REGISTER_AD, &regReqData, &pRet,
&wRet);

/* delete */
for (size_t i = 0; i < adReqData.retTokenCount; i++) {
free (adReqData.retToken[i]);
}
free (adReqData.retToken);
}
}

BarUiPrintSong (&app->settings, curSong, app->curStation->isQuickMix ?
PianoFindStationById (app->ph.stations,
curSong->stationId) : NULL);
Expand Down