We are no longer offering accounts on this server. Consider https://gitlab.freedesktop.org/ as a place to host projects.

index.php 15 KB
Newer Older
1 2
<?php

3
/* GNU FM -- a free network service for sharing your music listening habits
mattl's avatar
mattl committed
4

5
   Copyright (C) 2009 Free Software Foundation, Inc
mattl's avatar
mattl committed
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU Affero General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU Affero General Public License for more details.

   You should have received a copy of the GNU Affero General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.

*/

22
require_once('../database.php');
23 24
require_once('../api/ArtistXML.php');
require_once('../api/UserXML.php');
25
require_once('../api/TrackXML.php');
26

27
# Error constants
elleo's avatar
elleo committed
28 29 30 31 32 33 34 35 36 37 38 39 40
define('LFM_INVALID_SERVICE',	2);
define('LFM_INVALID_METHOD',	3);
define('LFM_INVALID_TOKEN',	4);
define('LFM_INVALID_FORMAT',	5);
define('LFM_INVALID_PARAMS',	6);
define('LFM_INVALID_RESOURCE',	7);
define('LFM_TOKEN_ERROR',	8);
define('LFM_INVALID_SESSION',	9);
define('LFM_INVALID_APIKEY',	10);
define('LFM_SERVICE_OFFLINE',	11);
define('LFM_SUBSCRIPTION_ERROR',12);
define('LFM_INVALID_SIGNATURE',	13);
define('LFM_SUBSCRIPTION_REQD',	18);
clint's avatar
clint committed
41 42 43 44
define('LFM_NOT_ENOUGH_CONTENT',	20);
define('LFM_NOT_ENOUGH_MEMBERS',	21);
define('LFM_NOT_ENOUGH_FANS',	22);
define('LFM_NOT_ENOUGH_NEIGHBORS',	23);
45 46 47

# Error descriptions as per API documentation
$error_text = array(
elleo's avatar
elleo committed
48 49 50 51 52 53 54 55 56 57 58 59
	LFM_INVALID_SERVICE		=> 'Invalid service - This service does not exist',
	LFM_INVALID_METHOD		=> 'Invalid Method - No method with that name in this package',
	LFM_INVALID_TOKEN		=> 'Invalid authentication token supplied',
	LFM_INVALID_FORMAT		=> 'Invalid format - This service doesn\'t exist in that format',
	LFM_INVALID_PARAMS		=> 'Invalid parameters - Your request is missing a required parameter',
	LFM_INVALID_RESOURCE		=> 'Invalid resource specified',
	LFM_TOKEN_ERROR			=> 'There was an error granting the request token. Please try again later',
	LFM_INVALID_SESSION		=> 'Invalid session key - Please re-authenticate',
	LFM_INVALID_APIKEY		=> 'Invalid API key - You must be granted a valid key by last.fm',
	LFM_SERVICE_OFFLINE		=> 'Service Offline - This service is temporarily offline. Try again later.',
	LFM_SUBSCRIPTION_ERROR		=> 'Subscription Error - The user needs to be subscribed in order to do that',
	LFM_INVALID_SIGNATURE		=> 'Invalid method signature supplied',
jurgbohn's avatar
jurgbohn committed
60 61 62 63
	LFM_SUBSCRIPTION_REQD		=> 'This user has no free radio plays left. Subscription required.',
	LFM_NOT_ENOUGH_CONTENT		=> 'There is not enough content to play this station',
	LFM_NOT_ENOUGH_MEMBERS		=> 'This group does not have enough members for radio',
	LFM_NOT_ENOUGH_FANS		=> 'This artist does not have enough fans for radio',
clint's avatar
clint committed
64
	LFM_NOT_ENOUGH_NEIGHBORS	=> 'Thare are not enough neighbors for radio'
65 66 67 68
);

# Resolves method= parameters to handler functions
$method_map = array(
69 70 71 72 73
	'auth.gettoken'			=> method_auth_getToken,
	'auth.getsession'		=> method_auth_getSession,
	'auth.getmobilesession'		=> method_auth_getMobileSession,
	'artist.getinfo'		=> method_artist_getInfo,
	'artist.gettoptracks'		=> method_artist_getTopTracks,
74
	'artist.gettoptags'		=> method_artist_getTopTags,
75 76 77 78
	'user.getinfo'			=> method_user_getInfo,
	'user.gettoptracks'		=> method_user_getTopTracks,
	'user.getrecenttracks'		=> method_user_getRecentTracks,
	'user.gettoptags'		=> method_user_getTopTags,
79
	'user.getlovedtracks'		=> method_user_getLovedTracks,
clint's avatar
clint committed
80
	'radio.tune'			=> method_radio_tune,
81
	'radio.getplaylist'		=> method_radio_getPlaylist,
82
	'track.gettoptags'		=> method_track_getTopTags,
83
	'track.gettags'			=> method_track_getTags,
84
	'track.ban'			=> method_track_ban,
85
	'track.love'			=> method_track_love,
86
);
87

88 89 90 91 92

/**
 * User methods
 */

93
function method_user_getRecentTracks() {
94
	if (!isset($_GET['user'])) {
95
		report_failure(LFM_INVALID_PARAMS);
96 97 98
	}

	header('Content-Type: text/xml');
Clint Adams's avatar
Clint Adams committed
99
	print(XML::prettyXML(UserXML::getRecentTracks($_GET['user'], $_GET['limit'])));
100 101
}

102
function method_user_getTopTags() {
103
	if (!isset($_GET['user'])) {
104
		report_failure(LFM_INVALID_PARAMS);
105 106 107 108 109 110 111
	}

        header('Content-Type: text/xml');
        print(XML::prettyXML(UserXML::getTopTags($_GET['user'])));
}


112
function method_user_getTopTracks() {
113
	if (!isset($_GET['user'])) {
114
		report_failure(LFM_INVALID_PARAMS);
115
	}
116

elleo's avatar
elleo committed
117
	header('Content-Type: text/xml');
118
	print(XML::prettyXML(UserXML::getTopTracks($_GET['user'], $_GET['period'])));
119 120
}

121
function method_user_getInfo() {
122
	if (!isset($_GET['user'])) {
123
		report_failure(LFM_INVALID_PARAMS);
124
	}
elleo's avatar
elleo committed
125
	header('Content-Type: text/xml');
126 127 128
	print(XML::prettyXML(UserXML::getInfo($_GET['user'])));
}

129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150
function method_user_getLovedTracks() {
	if (!isset($_GET['user'])) {
		report_failure(LFM_INVALID_PARAMS);
	}

	$user = $_GET['user'];
	if (isset($_GET['limit'])) {
		$limit = $_GET['limit'];
	} else {
		$limit = 50;
	}

	header('Content-Type: text/xml');
	print(XML::prettyXML(UserXML::getLovedTracks($user, $limit)));
}



/**
 * Artist methods
 */

151
function method_artist_getInfo() {
152
	if (!isset($_GET['artist'])) {
153
		report_failure(LFM_INVALID_PARAMS);
154
	}
elleo's avatar
elleo committed
155
	header('Content-Type: text/xml');
156
	print(XML::prettyXML(ArtistXML::getInfo($_GET['artist'])));
157 158
}

159
function method_artist_getTopTracks() {
160
	if (!isset($_GET['artist'])) {
161
		report_failure(LFM_INVALID_PARAMS);
162
	}
elleo's avatar
elleo committed
163
	header('Content-Type: text/xml');
164
	print(XML::prettyXML(ArtistXML::getTopTracks($_GET['artist'])));
165
}
166

167 168 169 170 171 172
function method_artist_getTopTags() {
	if (!isset($_GET['artist'])) {
		report_failure(LFM_INVALID_PARAMS);
	}
	header('Content-Type: text/xml');
	print(XML::prettyXML(ArtistXML::getTopTags($_GET['artist'])));
173
}
174

175 176 177 178
/**
 * Authentication methods
 */

179
function method_auth_getToken() {
clint's avatar
clint committed
180
	global $adodb;
181

182
	if (!isset($_GET['api_sig']) || !valid_api_sig($_GET['api_sig']))
183
		report_failure(LFM_INVALID_PARAMS);
184

185
	$key = md5(time() . rand());
186

clint's avatar
clint committed
187 188 189 190
	try {
	$result = $adodb->Execute('INSERT INTO Auth (token, expires) VALUES ('
		. $adodb->qstr($key) . ", "
		. (int)(time() + 3600)
191
		. ")");
clint's avatar
clint committed
192 193
	}
	catch (exception $e) {
194
		report_failure(LFM_SERVICE_OFFLINE);
clint's avatar
clint committed
195
	}
196

197
	print("<lfm status=\"ok\">\n");
elleo's avatar
elleo committed
198
	print("	<token>{$key}</token></lfm>");
199 200
}

201
function method_auth_getMobileSession() {
clint's avatar
clint committed
202
	global $adodb;
203 204 205 206 207 208 209

	if (!isset($_GET['api_sig']) || !valid_api_sig($_GET['api_sig']))
		report_failure(LFM_INVALID_SIGNATURE);

	if (!isset($_GET['authToken']))
		report_failure(LFM_INVALID_TOKEN);

210
	// Check for a token that is bound to a user
clint's avatar
clint committed
211 212 213 214 215
	try {
	$result = $adodb->GetRow('SELECT username, password FROM Users WHERE '
		. 'username = ' . $adodb->qstr($_GET['username']));
	}
	catch (exception $e) {
216
		report_failure(LFM_SERVICE_OFFLINE);
217
	}
218 219 220 221 222 223
	if (is_null($result)) {
		report_failure(LFM_INVALID_TOKEN);
	}

	list($username, $password) = $result;
	if (md5($username . $password) != $_GET['authToken']) {
224
		report_failure(LFM_INVALID_TOKEN);
225
	}
226 227 228 229 230

	$key = md5(time() . rand());
	$session = md5(time() . rand());

	// Update the Auth record with the new session key
clint's avatar
clint committed
231 232
	try {
	$result = $adodb->Execute('INSERT INTO Auth (token, sk, expires, username) '
233
		. 'VALUES ('
clint's avatar
clint committed
234 235 236 237
		. $adodb->qstr($key) . ', '
		. $adodb->qstr($session) . ', '
		. (int)(time() + 3600) . ', '
		. $adodb->qstr($username)
238
		. ')');
clint's avatar
clint committed
239 240
	}
	catch (exception $e) {
241
		report_failure(LFM_SERVICE_OFFLINE);
clint's avatar
clint committed
242
	}
243 244 245

	print("<lfm status=\"ok\">\n");
	print("	<session>\n");
elleo's avatar
elleo committed
246 247
	print("		<name>{$username}</name>\n");
	print("		<key>{$session}</key>\n");
248 249 250 251 252
	print("		<subscriber>0</subscriber>\n");
	print("	</session>\n");
	print("</lfm>");
}

253
function method_auth_getSession() {
clint's avatar
clint committed
254
	global $adodb;
255

256 257
	if (!isset($_GET['api_sig']) || !valid_api_sig($_GET['api_sig']))
		report_failure(LFM_INVALID_SIGNATURE);
258

259 260
	if (!isset($_GET['token']))
		report_failure(LFM_INVALID_TOKEN);
261

262
	// Check for a token that (1) is bound to a user, and (2) is not bound to a session
clint's avatar
clint committed
263 264 265
	try {
	$username = $adodb->GetOne('SELECT username FROM Auth WHERE '
		. 'token = ' . $adodb->qstr($_GET['token']) . ' AND '
266
		. 'username IS NOT NULL AND sk IS NULL');
clint's avatar
clint committed
267 268
	}
	catch (exception $e) {
269
		report_failure(LFM_SERVICE_OFFLINE);
clint's avatar
clint committed
270 271
	}
	if (!$username) {
272
		report_failure(LFM_INVALID_TOKEN);
clint's avatar
clint committed
273
	}
274

275
	$session = md5(time() . rand());
276

277
	// Update the Auth record with the new session key
clint's avatar
clint committed
278 279 280 281 282 283
	try {
	$result = $adodb->Execute('UPDATE Auth SET '
		. 'sk = ' . $adodb->qstr($session) . ' WHERE '
		. 'token = ' . $adodb->qstr($_GET['token']));
	}
	catch (exception $e) {
284
		report_failure(LFM_SERVICE_OFFLINE);
clint's avatar
clint committed
285
	}
286

287
	print("<lfm status=\"ok\">\n");
288
	print("	<session>\n");
elleo's avatar
elleo committed
289 290
	print("		<name>{$username}</name>\n");
	print("		<key>{$session}</key>\n");
291 292
	print("		<subscriber>0</subscriber>\n");
	print("	</session>\n");
293
	print("</lfm>");
294 295
}

clint's avatar
clint committed
296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359
function method_radio_tune() {
	global $adodb;

	if (!isset($_GET['api_sig']) || !valid_api_sig($_GET['api_sig']))
		report_failure(LFM_INVALID_SIGNATURE);

	if (!isset($_GET['station']))
		report_failure(LFM_INVALID_PARAMS);

	if (!isset($_GET['api_key']))
		report_failure(LFM_INVALID_PARAMS);

	if (!isset($_GET['sk']))
		report_failure(LFM_INVALID_PARAMS);

	try {
	$username = $adodb->GetOne('SELECT username FROM Auth WHERE '
		. 'token = ' . $adodb->qstr($_GET['token']) . ' AND '
		. 'username IS NOT NULL AND sk = '.$adodb->qstr($_GET['sk']));
	}
	catch (exception $e) {
		report_failure(LFM_SERVICE_OFFLINE);
	}
	if (!$username) {
		report_failure(LFM_INVALID_TOKEN);
	}

/*
 * Here we should tune the station.  The immediate problem is that
 * without radio handshake, the user will not necessarily have a
 * session in Radio_Sessions.
 *
 * After that's solved, we should either set $stationtype,
 * $stationname, $stationurl, or report_failure.
 */
	report_failure(LFM_SUBSCRIPTION_REQD);

	print("<lfm status=\"ok\">\n");
	print("	<station>\n");
	print("		<type>{$stationtype}</type>\n");
	print("		<name>{$stationname}</name>\n");
	print("		<url>{$stationurl}</url>\n");
	print("		<supportsdiscovery>0</supportsdiscovery>\n");
	print("	</station>\n");
	print("</lfm>");
}

function method_radio_getPlaylist() {
	global $adodb;

	if (!isset($_GET['api_sig']) || !valid_api_sig($_GET['api_sig']))
		report_failure(LFM_INVALID_SIGNATURE);

	if (!isset($_GET['api_key']))
		report_failure(LFM_INVALID_PARAMS);

	if (!isset($_GET['sk']))
		report_failure(LFM_INVALID_PARAMS);

/*
 * Here we should get the station based on the session key.  If
 * no station is tuned for that key, we should default to something
 * reasonable.
 *
clint's avatar
clint committed
360 361
 * Then we should return a playlist in a format not quite identical
 * to the one spit out
clint's avatar
clint committed
362 363 364
 * by xspf.php.
 */

365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431
        $res = $adodb->Execute('SELECT Track.name, Track.artist_name, Track.album_name FROM Track INNER JOIN Tags ON Track.name=Tags.track 
AND Track.artist_name=Tags.artist AND Track.album_name=Tags.album WHERE streamurl<>\'\' AND streamable=1 AND lower(tag) = \'folk\'');

$avail = $res->RecordCount();

$tr[0] = rand(0,$avail-1);
$tr[1] = rand(0,$avail-1);
$tr[2] = rand(0,$avail-1);
$tr[3] = rand(0,$avail-1);
$tr[4] = rand(0,$avail-1);
$tr = array_unique($tr);
// we should probably shuffle these here

$radiotracks = array();
$adodb->SetFetchMode(ADODB_FETCH_ASSOC);

        for($i=0; $i<count($tr); $i++) {

        $res->Move($tr[$i]);
        $row = $res->FetchRow();

        $track = new Track($row['name'], $row['artist_name']);
        $album = new Album($row['album_name'], $row['artist_name']);
        $artist = new Artist($row['artist_name']);

        if($track->duration == 0) {
                $duration = 180000;
        } else {
                $duration = $track->duration * 1000;
        }

        $radiotracks[$i]['location'] = $track->streamurl;
        $radiotracks[$i]['title'] = $track->name;
        $radiotracks[$i]['id'] = "0000";
        $radiotracks[$i]['album'] = $album->name;
        $radiotracks[$i]['creator'] = $artist->name;
        $radiotracks[$i]['duration'] = $duration;
        $radiotracks[$i]['image'] = $album->image;
        $radiotracks[$i]['artisturl'] = $artist->getURL();
        $radiotracks[$i]['albumurl'] = $album->getURL();
        $radiotracks[$i]['trackurl'] = $track->getURL();
        $radiotracks[$i]['downloadurl'] = $track->getURL();

        }

	print("<lfm status=\"ok\">\n");
	print("<playlist version=\"1\" xmlns=\"http://xspf.org/ns/0/\">\n");
	print("<title>Fake Playlist</title>\n");
	print("<creator>libre.fm</creator>\n");
	print("<link rel=\"http://libre.fm/expiry\">9999</link>\n");
	print("<trackList>\n");

        for($i=0; $i<count($tr); $i++) {
        print("<track>\n");
        print("<location>".urlencode($radiotracks[$i]['location'])."</location>\n");
        print("<title>".urlencode($radiotracks[$i]['title'])."</title>\n");
        print("<id>".urlencode($radiotracks[$i]['id'])."</id>\n");
        print("<album>".urlencode($radiotracks[$i]['album'])."</album>\n");
        print("<creator>".urlencode($radiotracks[$i]['creator'])."</creator>\n");
        print("<duration>".urlencode($radiotracks[$i]['duration'])."</duration>\n");
        print("<image>".urlencode($radiotracks[$i]['image'])."</image>\n");
        print("</track>\n");
	}

	print("</trackList>\n");
	print("</playlist>\n");
	print("</lfm>\n");
clint's avatar
clint committed
432 433 434

}

435 436 437 438
/**
 * Track methods
 */

439
function method_track_getTopTags() {
440
	if (!isset($_GET['artist']) || !isset($_GET['track'])) {
441
		report_failure(LFM_INVALID_PARAMS);
442 443 444
	}

	header('Content-Type: text/xml');
445
	print(XML::prettyXML(TrackXML::getTopTags($_GET['artist'], $_GET['track'])));
446 447
}

448 449 450
function method_track_getTags() {
	global $adodb;

451 452 453 454 455 456 457 458 459 460 461
	if (!isset($_GET['artist']) || !isset($_GET['track'])) {
		report_failure(LFM_INVALID_PARAMS);
	}

	$userid = get_userid();

	header('Content-Type: text/xml');
	print(XML::prettyXML(TrackXML::getTags($_GET['artist'], $_GET['track'], $userid)));
}

function method_track_ban() {
462
	if (!isset($_POST['artist']) || !isset($_POST['track'])) {
463 464 465 466 467 468
		report_failure(LFM_INVALID_PARAMS);
	}

	$userid = get_userid();

	header('Content-Type: text/xml');
469
	print(XML::prettyXML(TrackXML::ban($_POST['artist'], $_POST['track'], $userid)));
470 471
}

472 473 474 475 476 477 478 479 480 481
function method_track_love() {
	if (!isset($_POST['artist']) || !isset($_POST['track'])) {
		report_failure(LFM_INVALID_PARAMS);
	}

	$userid = get_userid();

	header('Content-Type: text/xml');
	print(XML::prettyXML(TrackXML::love($_POST['artist'], $_POST['track'], $userid)));
}
482 483 484 485

function get_userid() {
	global $adodb;

486
	if (!isset($_REQUEST['sk'])) {
487 488 489 490
		report_failure(LFM_INVALID_PARAMS);
	}

	$username = $adodb->GetOne('SELECT username FROM Auth WHERE '
491
		. 'sk = ' . $adodb->qstr($_REQUEST['sk']) . ' AND '
492
		. 'username IS NOT NULL');
493 494 495 496 497 498 499 500

	if (!$username) {
		report_failure(LFM_INVALID_SESSION);
	}

	$userid = $adodb->GetOne('SELECT uniqueid FROM Users WHERE '
		. 'username = ' . $adodb->qstr($username));

501
	return $userid;
502 503
}

504
function valid_api_key($key) {
505
	return strlen($key) == 32;
506 507 508
}

function valid_api_sig($sig) {
509
	return strlen($sig) == 32;
510 511 512
}

function report_failure($code) {
513
	global $error_text;
514

515
	print("<lfm status=\"failed\">\n");
elleo's avatar
elleo committed
516
	print("	<error code=\"{$code}\">".$error_text[$code]."</error></lfm>");
517
	die();
518 519
}

520 521
$_REQUEST['method'] = strtolower($_REQUEST['method']);
if (!isset($_REQUEST['method']) || !isset($method_map[$_REQUEST['method']]))
522
	report_failure(LFM_INVALID_METHOD);
523

524
$method = $method_map[$_REQUEST['method']];
525
$method();