Skip to content
This repository was archived by the owner on May 24, 2026. It is now read-only.
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