2
Copyright (c) 2008-2012
3
Lars-Dominik Braun <lars@6xq.net>
5
Permission is hereby granted, free of charge, to any person obtaining a copy
6
of this software and associated documentation files (the "Software"), to deal
7
in the Software without restriction, including without limitation the rights
8
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
copies of the Software, and to permit persons to whom the Software is
10
furnished to do so, subject to the following conditions:
12
The above copyright notice and this permission notice shall be included in
13
all copies or substantial portions of the Software.
15
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25
#define _BSD_SOURCE /* required by strdup() */
26
#define _DARWIN_C_SOURCE /* strdup() on OS X */
33
/* needed for urlencode */
39
/* convert audio format id to string
41
* @return constant string
43
static const char *PianoAudioFormatToString (PianoAudioFormat_t format) {
45
case PIANO_AF_AACPLUS_LO:
46
return "HTTP_32_AACPLUS";
49
case PIANO_AF_AACPLUS:
50
return "HTTP_64_AACPLUS";
54
return "HTTP_128_MP3";
58
return "HTTP_192_MP3";
67
/* prepare piano request (initializes request type, urlpath and postData)
69
* @param request structure
72
PianoReturn_t PianoRequest (PianoHandle_t *ph, PianoRequest_t *req,
73
PianoRequestType_t type) {
74
PianoReturn_t ret = PIANO_RET_OK;
75
const char *jsonSendBuf;
76
const char *method = NULL;
77
json_object *j = json_object_new_object ();
78
/* corrected timestamp */
79
time_t timestamp = time (NULL) - ph->timeOffset;
80
bool encrypted = true;
86
/* no tls by default */
90
case PIANO_REQUEST_LOGIN: {
91
/* authenticate user */
92
PianoRequestDataLogin_t *logindata = req->data;
94
assert (logindata != NULL);
96
switch (logindata->step) {
101
json_object_object_add (j, "username",
102
json_object_new_string (ph->partner.user));
103
json_object_object_add (j, "password",
104
json_object_new_string (ph->partner.password));
105
json_object_object_add (j, "deviceModel",
106
json_object_new_string (ph->partner.device));
107
json_object_object_add (j, "version",
108
json_object_new_string ("5"));
109
json_object_object_add (j, "includeUrls",
110
json_object_new_boolean (true));
111
snprintf (req->urlPath, sizeof (req->urlPath),
112
PIANO_RPC_PATH "method=auth.partnerLogin");
116
char *urlencAuthToken;
120
json_object_object_add (j, "loginType",
121
json_object_new_string ("user"));
122
json_object_object_add (j, "username",
123
json_object_new_string (logindata->user));
124
json_object_object_add (j, "password",
125
json_object_new_string (logindata->password));
126
json_object_object_add (j, "partnerAuthToken",
127
json_object_new_string (ph->partner.authToken));
128
json_object_object_add (j, "syncTime",
129
json_object_new_int (timestamp));
131
urlencAuthToken = WaitressUrlEncode (ph->partner.authToken);
132
assert (urlencAuthToken != NULL);
133
snprintf (req->urlPath, sizeof (req->urlPath),
134
PIANO_RPC_PATH "method=auth.userLogin&"
135
"auth_token=%s&partner_id=%i", urlencAuthToken,
137
free (urlencAuthToken);
145
case PIANO_REQUEST_GET_STATIONS: {
146
/* get stations, user must be authenticated */
147
assert (ph->user.listenerId != NULL);
148
method = "user.getStationList";
152
case PIANO_REQUEST_GET_PLAYLIST: {
153
/* get playlist for specified station */
154
PianoRequestDataGetPlaylist_t *reqData = req->data;
156
assert (reqData != NULL);
157
assert (reqData->station != NULL);
158
assert (reqData->station->id != NULL);
159
assert (reqData->format != PIANO_AF_UNKNOWN);
163
json_object_object_add (j, "stationToken",
164
json_object_new_string (reqData->station->id));
165
json_object_object_add (j, "additionalAudioUrl",
166
json_object_new_string (PianoAudioFormatToString (reqData->format)));
168
method = "station.getPlaylist";
172
case PIANO_REQUEST_ADD_FEEDBACK: {
173
/* low-level, don't use directly (see _RATE_SONG and _MOVE_SONG) */
174
PianoRequestDataAddFeedback_t *reqData = req->data;
176
assert (reqData != NULL);
177
assert (reqData->trackToken != NULL);
178
assert (reqData->rating != PIANO_RATE_NONE);
180
json_object_object_add (j, "trackToken",
181
json_object_new_string (reqData->trackToken));
182
json_object_object_add (j, "isPositive",
183
json_object_new_boolean (reqData->rating == PIANO_RATE_LOVE));
185
method = "station.addFeedback";
189
case PIANO_REQUEST_RENAME_STATION: {
190
PianoRequestDataRenameStation_t *reqData = req->data;
192
assert (reqData != NULL);
193
assert (reqData->station != NULL);
194
assert (reqData->newName != NULL);
196
json_object_object_add (j, "stationToken",
197
json_object_new_string (reqData->station->id));
198
json_object_object_add (j, "stationName",
199
json_object_new_string (reqData->newName));
201
method = "station.renameStation";
205
case PIANO_REQUEST_DELETE_STATION: {
207
PianoStation_t *station = req->data;
209
assert (station != NULL);
210
assert (station->id != NULL);
212
json_object_object_add (j, "stationToken",
213
json_object_new_string (station->id));
215
method = "station.deleteStation";
219
case PIANO_REQUEST_SEARCH: {
220
/* search for artist/song title */
221
PianoRequestDataSearch_t *reqData = req->data;
223
assert (reqData != NULL);
224
assert (reqData->searchStr != NULL);
226
json_object_object_add (j, "searchText",
227
json_object_new_string (reqData->searchStr));
229
method = "music.search";
233
case PIANO_REQUEST_CREATE_STATION: {
234
/* create new station from specified musicid (type=mi, get one by
235
* performing a search) or shared station id (type=sh) */
236
PianoRequestDataCreateStation_t *reqData = req->data;
238
assert (reqData != NULL);
239
assert (reqData->id != NULL);
241
json_object_object_add (j, "musicToken",
242
json_object_new_string (reqData->id));
244
method = "station.createStation";
248
case PIANO_REQUEST_ADD_SEED: {
249
/* add another seed to specified station */
250
PianoRequestDataAddSeed_t *reqData = req->data;
252
assert (reqData != NULL);
253
assert (reqData->station != NULL);
254
assert (reqData->musicId != NULL);
256
json_object_object_add (j, "musicToken",
257
json_object_new_string (reqData->musicId));
258
json_object_object_add (j, "stationToken",
259
json_object_new_string (reqData->station->id));
261
method = "station.addMusic";
265
case PIANO_REQUEST_ADD_TIRED_SONG: {
266
/* ban song for a month from all stations */
267
PianoSong_t *song = req->data;
269
assert (song != NULL);
271
json_object_object_add (j, "trackToken",
272
json_object_new_string (song->trackToken));
274
method = "user.sleepSong";
278
case PIANO_REQUEST_SET_QUICKMIX: {
279
/* select stations included in quickmix (see useQuickMix flag of
281
PianoStation_t *curStation = ph->stations;
282
json_object *a = json_object_new_array ();
284
while (curStation != NULL) {
285
/* quick mix can't contain itself */
286
if (curStation->useQuickMix && !curStation->isQuickMix) {
287
json_object_array_add (a,
288
json_object_new_string (curStation->id));
291
curStation = curStation->next;
294
json_object_object_add (j, "quickMixStationIds", a);
296
method = "user.setQuickMix";
300
case PIANO_REQUEST_GET_GENRE_STATIONS: {
301
/* receive list of pandora's genre stations */
302
method = "station.getGenreStations";
306
case PIANO_REQUEST_TRANSFORM_STATION: {
307
/* transform shared station into private */
308
PianoStation_t *station = req->data;
310
assert (station != NULL);
312
json_object_object_add (j, "stationToken",
313
json_object_new_string (station->id));
315
method = "station.transformSharedStation";
319
case PIANO_REQUEST_EXPLAIN: {
320
/* explain why particular song was played */
321
PianoRequestDataExplain_t *reqData = req->data;
323
assert (reqData != NULL);
324
assert (reqData->song != NULL);
326
json_object_object_add (j, "trackToken",
327
json_object_new_string (reqData->song->trackToken));
329
method = "track.explainTrack";
333
case PIANO_REQUEST_BOOKMARK_SONG: {
335
PianoSong_t *song = req->data;
337
assert (song != NULL);
339
json_object_object_add (j, "trackToken",
340
json_object_new_string (song->trackToken));
342
method = "bookmark.addSongBookmark";
346
case PIANO_REQUEST_BOOKMARK_ARTIST: {
347
/* bookmark artist */
348
PianoSong_t *song = req->data;
350
assert (song != NULL);
352
json_object_object_add (j, "trackToken",
353
json_object_new_string (song->trackToken));
355
method = "bookmark.addArtistBookmark";
359
case PIANO_REQUEST_GET_STATION_INFO: {
360
/* get station information (seeds and feedback) */
361
PianoRequestDataGetStationInfo_t *reqData = req->data;
363
assert (reqData != NULL);
364
assert (reqData->station != NULL);
366
json_object_object_add (j, "stationToken",
367
json_object_new_string (reqData->station->id));
368
json_object_object_add (j, "includeExtendedAttributes",
369
json_object_new_boolean (true));
371
method = "station.getStation";
375
case PIANO_REQUEST_DELETE_FEEDBACK: {
376
PianoSong_t *song = req->data;
378
assert (song != NULL);
380
json_object_object_add (j, "feedbackId",
381
json_object_new_string (song->feedbackId));
383
method = "station.deleteFeedback";
387
case PIANO_REQUEST_DELETE_SEED: {
388
PianoRequestDataDeleteSeed_t *reqData = req->data;
391
assert (reqData != NULL);
392
assert (reqData->song != NULL || reqData->artist != NULL ||
393
reqData->station != NULL);
395
if (reqData->song != NULL) {
396
seedId = reqData->song->seedId;
397
} else if (reqData->artist != NULL) {
398
seedId = reqData->artist->seedId;
399
} else if (reqData->station != NULL) {
400
seedId = reqData->station->seedId;
403
assert (seedId != NULL);
405
json_object_object_add (j, "seedId",
406
json_object_new_string (seedId));
408
method = "station.deleteMusic";
412
/* "high-level" wrapper */
413
case PIANO_REQUEST_RATE_SONG: {
415
PianoRequestDataRateSong_t *reqData = req->data;
417
assert (reqData != NULL);
418
assert (reqData->song != NULL);
419
assert (reqData->rating != PIANO_RATE_NONE);
421
PianoRequestDataAddFeedback_t transformedReqData;
422
transformedReqData.stationId = reqData->song->stationId;
423
transformedReqData.trackToken = reqData->song->trackToken;
424
transformedReqData.rating = reqData->rating;
425
req->data = &transformedReqData;
427
/* create request data (url, post data) */
428
ret = PianoRequest (ph, req, PIANO_REQUEST_ADD_FEEDBACK);
429
/* and reset request type/data */
430
req->type = PIANO_REQUEST_RATE_SONG;
437
case PIANO_REQUEST_MOVE_SONG: {
438
/* move song to a different station, needs two requests */
439
PianoRequestDataMoveSong_t *reqData = req->data;
440
PianoRequestDataAddFeedback_t transformedReqData;
442
assert (reqData != NULL);
443
assert (reqData->song != NULL);
444
assert (reqData->from != NULL);
445
assert (reqData->to != NULL);
446
assert (reqData->step < 2);
448
transformedReqData.trackToken = reqData->song->trackToken;
449
req->data = &transformedReqData;
451
switch (reqData->step) {
453
transformedReqData.stationId = reqData->from->id;
454
transformedReqData.rating = PIANO_RATE_BAN;
458
transformedReqData.stationId = reqData->to->id;
459
transformedReqData.rating = PIANO_RATE_LOVE;
463
/* create request data (url, post data) */
464
ret = PianoRequest (ph, req, PIANO_REQUEST_ADD_FEEDBACK);
465
/* and reset request type/data */
466
req->type = PIANO_REQUEST_MOVE_SONG;
474
/* standard parameter */
475
if (method != NULL) {
476
char *urlencAuthToken;
478
assert (ph->user.authToken != NULL);
480
urlencAuthToken = WaitressUrlEncode (ph->user.authToken);
481
assert (urlencAuthToken != NULL);
483
snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH
484
"method=%s&auth_token=%s&partner_id=%i&user_id=%s", method,
485
urlencAuthToken, ph->partner.id, ph->user.listenerId);
487
free (urlencAuthToken);
489
json_object_object_add (j, "userAuthToken",
490
json_object_new_string (ph->user.authToken));
491
json_object_object_add (j, "syncTime",
492
json_object_new_int (timestamp));
496
jsonSendBuf = json_object_to_json_string (j);
498
if ((req->postData = PianoEncryptString (ph->partner.out,
499
jsonSendBuf)) == NULL) {
500
ret = PIANO_RET_OUT_OF_MEMORY;
503
req->postData = strdup (jsonSendBuf);