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

index.php 14 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(
elleo's avatar
elleo committed
69 70 71 72 73 74
	'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,
	'user.getinfo'			=> method_user_getinfo,
clint's avatar
clint committed
75
	'user.gettoptracks'		=> method_user_gettoptracks,
76
	'user.getrecenttracks'		=> method_user_getrecenttracks,
77
	'user.gettoptags'		=> method_user_gettoptags,
clint's avatar
clint committed
78
	'radio.tune'			=> method_radio_tune,
79
	'radio.getplaylist'		=> method_radio_getPlaylist,
80
	'track.gettoptags'		=> method_track_getTopTags,
81
	'track.gettags'			=> method_track_getTags,
82
	'track.ban'			=> method_track_ban,
83
);
84

85 86 87 88 89 90
function method_user_getrecenttracks() {
	if (!isset($_GET['user'])) {
		report_failure(LFM_INVALID_SIGNATURE);
	}

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

94 95 96 97 98 99 100 101 102 103
function method_user_gettoptags() {
	if (!isset($_GET['user'])) {
		report_failure(LFM_INVALID_SIGNATURE);
	}

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


104
function method_user_gettoptracks() {
105
	if (!isset($_GET['user'])) {
106
		report_failure(LFM_INVALID_SIGNATURE);
107
	}
108

elleo's avatar
elleo committed
109
	header('Content-Type: text/xml');
110
	print(XML::prettyXML(UserXML::getTopTracks($_GET['user'], $_GET['period'])));
111 112
}

113
function method_user_getinfo() {
114
	if (!isset($_GET['user'])) {
115
		report_failure(LFM_INVALID_SIGNATURE);
116
	}
elleo's avatar
elleo committed
117
	header('Content-Type: text/xml');
118 119 120 121
	print(XML::prettyXML(UserXML::getInfo($_GET['user'])));
}

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

129
function method_artist_gettoptracks() {
130
	if (!isset($_GET['artist'])) {
131
	report_failure(LFM_INVALID_SIGNATURE);
132
	}
elleo's avatar
elleo committed
133
	header('Content-Type: text/xml');
134
	print(XML::prettyXML(ArtistXML::getTopTracks($_GET['artist'])));
135 136

}
137 138

function method_auth_gettoken() {
clint's avatar
clint committed
139
	global $adodb;
140

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

144
	$key = md5(time() . rand());
145

clint's avatar
clint committed
146 147 148 149
	try {
	$result = $adodb->Execute('INSERT INTO Auth (token, expires) VALUES ('
		. $adodb->qstr($key) . ", "
		. (int)(time() + 3600)
150
		. ")");
clint's avatar
clint committed
151 152
	}
	catch (exception $e) {
153
		report_failure(LFM_SERVICE_OFFLINE);
clint's avatar
clint committed
154
	}
155

156
	print("<lfm status=\"ok\">\n");
elleo's avatar
elleo committed
157
	print("	<token>{$key}</token></lfm>");
158 159
}

160
function method_auth_getmobilesession() {
clint's avatar
clint committed
161
	global $adodb;
162 163 164 165 166 167 168

	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);

169
	// Check for a token that is bound to a user
clint's avatar
clint committed
170 171 172 173 174
	try {
	$result = $adodb->GetRow('SELECT username, password FROM Users WHERE '
		. 'username = ' . $adodb->qstr($_GET['username']));
	}
	catch (exception $e) {
175
		report_failure(LFM_SERVICE_OFFLINE);
176
	}
177 178 179 180 181 182
	if (is_null($result)) {
		report_failure(LFM_INVALID_TOKEN);
	}

	list($username, $password) = $result;
	if (md5($username . $password) != $_GET['authToken']) {
183
		report_failure(LFM_INVALID_TOKEN);
184
	}
185 186 187 188 189

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

	// Update the Auth record with the new session key
clint's avatar
clint committed
190 191
	try {
	$result = $adodb->Execute('INSERT INTO Auth (token, sk, expires, username) '
192
		. 'VALUES ('
clint's avatar
clint committed
193 194 195 196
		. $adodb->qstr($key) . ', '
		. $adodb->qstr($session) . ', '
		. (int)(time() + 3600) . ', '
		. $adodb->qstr($username)
197
		. ')');
clint's avatar
clint committed
198 199
	}
	catch (exception $e) {
200
		report_failure(LFM_SERVICE_OFFLINE);
clint's avatar
clint committed
201
	}
202 203 204

	print("<lfm status=\"ok\">\n");
	print("	<session>\n");
elleo's avatar
elleo committed
205 206
	print("		<name>{$username}</name>\n");
	print("		<key>{$session}</key>\n");
207 208 209 210 211
	print("		<subscriber>0</subscriber>\n");
	print("	</session>\n");
	print("</lfm>");
}

212
function method_auth_getsession() {
clint's avatar
clint committed
213
	global $adodb;
214

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

218 219
	if (!isset($_GET['token']))
		report_failure(LFM_INVALID_TOKEN);
220

221
	// Check for a token that (1) is bound to a user, and (2) is not bound to a session
clint's avatar
clint committed
222 223 224
	try {
	$username = $adodb->GetOne('SELECT username FROM Auth WHERE '
		. 'token = ' . $adodb->qstr($_GET['token']) . ' AND '
225
		. 'username IS NOT NULL AND sk IS NULL');
clint's avatar
clint committed
226 227
	}
	catch (exception $e) {
228
		report_failure(LFM_SERVICE_OFFLINE);
clint's avatar
clint committed
229 230
	}
	if (!$username) {
231
		report_failure(LFM_INVALID_TOKEN);
clint's avatar
clint committed
232
	}
233

234
	$session = md5(time() . rand());
235

236
	// Update the Auth record with the new session key
clint's avatar
clint committed
237 238 239 240 241 242
	try {
	$result = $adodb->Execute('UPDATE Auth SET '
		. 'sk = ' . $adodb->qstr($session) . ' WHERE '
		. 'token = ' . $adodb->qstr($_GET['token']));
	}
	catch (exception $e) {
243
		report_failure(LFM_SERVICE_OFFLINE);
clint's avatar
clint committed
244
	}
245

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

clint's avatar
clint committed
255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318
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
319 320
 * Then we should return a playlist in a format not quite identical
 * to the one spit out
clint's avatar
clint committed
321 322 323
 * by xspf.php.
 */

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 360 361 362 363 364 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
        $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
391 392 393

}

394 395 396 397
/**
 * Track methods
 */

398
function method_track_getTopTags() {
399 400 401 402 403
	if (!isset($_GET['artist']) || !isset($_GET['track'])) {
		report_failure(LFM_INVALID_SIGNATURE);
	}

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

407 408 409
function method_track_getTags() {
	global $adodb;

410 411 412 413 414 415 416 417 418 419 420
	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() {
421
	if (!isset($_POST['artist']) || !isset($_POST['track'])) {
422 423 424 425 426 427
		report_failure(LFM_INVALID_PARAMS);
	}

	$userid = get_userid();

	header('Content-Type: text/xml');
428
	print(XML::prettyXML(TrackXML::ban($_POST['artist'], $_POST['track'], $userid)));
429 430 431 432 433 434
}


function get_userid() {
	global $adodb;

435
	if (!isset($_REQUEST['sk'])) {
436 437 438 439
		report_failure(LFM_INVALID_PARAMS);
	}

	$username = $adodb->GetOne('SELECT username FROM Auth WHERE '
440 441
		. 'sk = ' . $adodb->qstr($_GET['sk']) . ' AND '
		. 'username IS NOT NULL');
442 443 444 445 446 447 448 449

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

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

450
	return $userid;
451 452
}

453
function valid_api_key($key) {
454
	return strlen($key) == 32;
455 456 457
}

function valid_api_sig($sig) {
458
	return strlen($sig) == 32;
459 460 461
}

function report_failure($code) {
462
	global $error_text;
463

464
	print("<lfm status=\"failed\">\n");
elleo's avatar
elleo committed
465
	print("	<error code=\"{$code}\">".$error_text[$code]."</error></lfm>");
466
	die();
467 468
}

469 470
$_REQUEST['method'] = strtolower($_REQUEST['method']);
if (!isset($_REQUEST['method']) || !isset($method_map[$_REQUEST['method']]))
471
	report_failure(LFM_INVALID_METHOD);
472

473
$method = $method_map[$_REQUEST['method']];
474
$method();