scrobble-utils.php 11.4 KB
Newer Older
1 2
<?php

Jonas Haraldsson's avatar
Jonas Haraldsson committed
3
/* GNU FM -- a free network service for sharing your music listening habits
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21

   Copyright (C) 2013 Free Software Foundation, Inc

   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 23 24 25 26
/**
 * Functions used by TrackXML.php:scrobble() and TrackXML.php:updateNowPlaying()
 * Similar but not identical to gnukebox/scrobble-utils.php
 */

27 28 29
require_once('database.php');


30
/**
31
 * Get artist ID and add artist to database if it doesnt already exist.
32 33 34 35
 *
 * @param string artist		Artist name.
 * @return int				Artist ID.
 */
36
function getArtistID($artist) {
37 38
	global $adodb;

39
	$query = 'SELECT id FROM Artist WHERE name=?';
40
	$params = array($artist);
41
	$artist_id = $adodb->GetOne($query, $params);
42

43
	if (!$artist_id) {
44 45 46 47
		// Artist doesn't exist, so we create them
		$query = 'INSERT INTO Artist (name) VALUES (?)';
		$params = array($artist);
		$res = $adodb->Execute($query, $params);
48
		return getArtistID($artist);
49 50
	} else {
		return $artist_id;
51
	}
52 53
}

54
/**
55
 * Get album ID and add album to database if it doesnt already exist.
56 57 58 59 60
 *
 * @param string artist		Artist name.
 * @param string album		Album name.
 * @return int				Album ID.
 *
61
 *	@todo Maybe we should return artist ID too, we will need it when db gets normalized
62
 */
63
function getAlbumID($artist, $album) {
64 65
	global $adodb;

66
	$query = 'SELECT id FROM Album WHERE name=? AND artist_name=?';
67
	$params = array($album, $artist);
68
	$album_id = $adodb->GetOne($query, $params);
69

70
	if (!$album_id) {
71 72 73
		// Album doesn't exist, so create it

		// First check if artist exist, if not create it
74
		$artist_id = getArtistID($artist);
75 76 77 78

		$query = 'INSERT INTO Album (name, artist_name) VALUES (?,?)';
		$params = array($album, $artist);
		$adodb->Execute($query, $params);
79
		return getAlbumID($artist, $album);
80 81
	} else {
		return $album_id;
82
	}
83 84
}

85
/**
86
 * Get track ID and add track to database if it doesnt already exist.
87 88 89 90 91
 *
 * @param string artist		Artist name.
 * @param string album		Album name.
 * @param string track		Track name.
 * @param string mbid		Track's musicbrainz ID.
92
 * @param int duration		Track length in seconds.
93
 * @return int				Track ID.
94 95
 *
 */
96
function getTrackID($artist, $album, $track, $mbid, $duration) {
97 98
	global $adodb;

99
	if ($album === null) {
100
		$query = 'SELECT id FROM Track WHERE name=? AND artist_name=? AND album_name IS NULL';
101
		$params = array($track, $artist);
102 103 104
	} else {
		$query = 'SELECT id FROM Track WHERE name=? AND artist_name=? AND album_name=?';
		$params = array($track, $artist, $album);
105
	}
106
	$track_id = $adodb->GetOne($query, $params);
107

108
	if (!$track_id) {
109
		// First check if artist and album exists, if not create them
110
		if ($album === null) {
111
			$artist_id = getArtistID($artist);
112 113
		} else {
			$album_id = getAlbumID($artist, $album);
114 115 116
		}
		
		// Create new track
117 118
		$query = 'INSERT INTO Track (name, artist_name, album_name, mbid, duration) VALUES (?,?,?,?,?)';
		$params = array($track, $artist, $album, $mbid, $duration);
119
		$adodb->Execute($query, $params);
120
		return getTrackID($artist, $album, $track, $mbid, $duration);
121
	} else {
122
		return $track_id;
123 124 125
	}
}

126
/**
127
 * Get scrobble_track ID and add track to Scrobble_Track db table if it doesnt already exist.
128
 *
129 130 131 132 133 134 135
 * @param string artist		Artist name.
 * @param string album		Album name.
 * @param string track		Track name.
 * @param string mbid		Track musicbrainz ID.
 * @param int duration		Track length in seconds.
 * @param int track_id		Track ID in Track database table
 * @return int				Scrobble_Track ID.
136
 */
137
function getScrobbleTrackID($artist, $album, $track, $mbid, $duration, $track_id) {
138 139 140 141 142
	global $adodb;

	$query = 'SELECT id FROM Scrobble_Track WHERE name=lower(?) AND artist=lower(?)';
	$params = array($track, $artist);

143 144 145
	if ($album === null) {
		$query .= ' AND album IS NULL';
	} else {
146 147 148 149
		$query .= ' AND album=lower(?)';
		$params[] = $album;
	}

150 151 152
	if ($mbid === null) {
		$query .= ' AND mbid IS NULL';
	} else {
153 154 155 156 157 158 159 160 161 162
		$query .= ' AND mbid=lower(?)';
		$params[] = $mbid;
	}

	$scrobbletrack_id = $adodb->GetOne($query, $params);

	if (!$scrobbletrack_id) {
		$query = 'INSERT INTO Scrobble_Track (name, artist, album, mbid, track) VALUES (lower(?), lower(?), lower(?), lower(?), ?)';
		$params = array($track, $artist, $album, $mbid, $track_id);
		$res = $adodb->Execute($query, $params);
163
		return getScrobbleTrackID($artist, $album, $track, $mbid, $duration, $track_id);
164 165 166 167 168
	} else {
		return $scrobbletrack_id;
	}
}

169 170 171 172 173
/**
 * Correct artist/album/track/mbid/timestamp input
 *
 * @param mixed input Input to be corrected.
 * @param string type Type of input to be corrected.
174
 * @return array Array(mixed $old_input, mixed $corrected_input, int $corrected)
175 176 177 178 179 180
 *
 */
function correctInput($input, $type) {
	$old = $input;
	$new = $old;

181
	if ($type == 'artist' || $type == 'album' || $type == 'track' || $type == 'albumartist') {
Jonas Haraldsson's avatar
Jonas Haraldsson committed
182 183 184

		//Limit strings to 255 chars
		switch (mb_detect_encoding($new)) {
185 186 187 188 189 190 191 192
		case 'ASCII':
		case 'UTF-8':
			$new = mb_strcut($new, 0, 255, 'UTF-8');
			break;
		default:
			$new = null;
		}

Jonas Haraldsson's avatar
Jonas Haraldsson committed
193
		// Remove spam and trim whitespace
194 195 196 197 198 199
		$new = str_replace(' (PREVIEW: buy it at www.magnatune.com)', '', $new);
		$new = trim($new);

		if (empty($new)) {
			$new = null;
		}
Jonas Haraldsson's avatar
Jonas Haraldsson committed
200

201 202 203 204 205 206 207 208 209 210 211
	} else if ($type == 'mbid') {
		if (isset($new)) {
			$new = strtolower(rtrim($new));
			if (preg_match('/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/', $new)) {
				//do nothing
			} else {
				$new = null;
			}
		} else {
			$new = null;
		}
Jonas Haraldsson's avatar
Jonas Haraldsson committed
212

213
	} else if ($type == 'timestamp') {
Jonas Haraldsson's avatar
Jonas Haraldsson committed
214
		$new = (int) $new;
215

216
	} else if ($type == 'duration') {
217 218 219 220 221
		if($new) {
			$new = (int) $new;
		} else {
			$new = null;
		}
222 223 224 225 226 227 228

	} else if ($type == 'tracknumber') {
		if($new) {
			$new = (int) $new;
		}else {
			$new = null;
		}
229 230
	}

231
	$result = array($old, $new, (int)($old != $new));
232 233 234 235 236 237
	return $result;
}

/**
 * Decide if and why we should ignore a track
 *
238 239
 * @param string input Input data
 * @param string type Type of input data
240 241
 * @return array Array(int $ignored_code, string $ignored_message)
 */
242
function ignoreInput($input, $type) {
243 244 245
	$ignored_code = 0;
	$ignored_message = '';

246
	if ($type == 'artist' && empty($input)) {
247 248 249
		$ignored_code = 1;
		$ignored_message = 'Artist was ignored';
	}
250 251

	if ($type == 'track' && empty($input)) {
252 253 254
		$ignored_code = 2;
		$ignored_message = 'Track was ignored';
	}
255 256 257 258 259 260 261 262 263 264 265 266 267

	if ($type == 'timestamp') {
		$timestamp_upperlimit = time() + 300;
		$timestamp_lowerlimit = 1009000000;
	
		if ($input > $timestamp_upperlimit) {
			$ignored_message = 'Timestamp is too new';
			$ignored_code = 3;
		}
		if ($input < $timestamp_lowerlimit) {
			$ignored_message = 'Timestamp is too old';
			$ignored_code = 4;
		}
268 269 270 271
	}

	return array($ignored_code, $ignored_message);
}
272 273

/**
274 275
 * Prepare a track for entering the database.
 * Tries to correct a track's data or marks it as invalid
276
 *
277 278 279 280
 * @param array t Array of track data.
 * @param int userid User ID.
 * @param string type Type of track, 'nowplaying' or 'scrobble'.
 * @return array Same array as t array, but with corrected data and added metadata.
281
 */
282 283 284 285 286 287
function prepareTrack($userid, $t, $type) {
	list($t['track_old'], $t['track'], $t['track_corrected']) = correctInput($t['track'], 'track');
	list($t['artist_old'], $t['artist'], $t['artist_corrected']) = correctInput($t['artist'], 'artist');
	list($t['album_old'], $t['album'], $t['album_corrected']) = correctInput($t['album'], 'album');
	list($t['mbid_old'], $t['mbid'], $t['mbid_corrected']) = correctInput($t['mbid'], 'mbid');
	list($t['duration_old'], $t['duration'], $t['duration_corrected']) = correctInput($t['duration'], 'duration');
288 289
	list($t['albumartist_old'], $t['albumartist'], $t['albumartist_corrected']) = correctInput($t['albumartist'], 'albumartist');
	list($t['tracknumber_old'], $t['tracknumber'], $t['tracknumber_corrected']) = correctInput($t['tracknumber'], 'tracknumber');
290 291 292 293 294

	//TODO not pretty
	list($t['ignored_code'], $t['ignored_message']) = ignoreInput($t['artist'], 'artist');
	if($t['ignored_code'] === 0) {
		list($t['ignored_code'], $t['ignored_message']) = ignoreInput($t['track'], 'track');
295 296
	}

297 298 299 300
	if ($type == 'scrobble') {
		list($t['timestamp_old'], $t['timestamp'], $t['timestamp_corrected']) = correctInput($t['timestamp'], 'timestamp');

		if($t['ignored_code'] === 0) {
301
			list($t['ignored_code'], $t['ignored_message']) = ignoreInput($t['timestamp'], 'timestamp');
302
		}
303
		if ($t['ignored_code'] === 0) {
304 305
			$exists = scrobbleExists($userid, $t['artist'], $t['track'], $t['timestamp']);
			if ($exists) {
306 307
				$t['ignored_code'] = 91; // GNU FM specific
				$t['ignored_message'] = 'Already scrobbled';
308 309 310 311
			}
		}
	}
	return $t;
312
}
313

Jonas Haraldsson's avatar
Jonas Haraldsson committed
314 315 316 317 318 319 320 321 322
/**
 * Check if a scrobble has already been added to database.
 *
 * @param int userid		User ID
 * @param string artist		Artist name
 * @param string track		Track name
 * @param int time			Timestamp
 * @return bool				True is scrobble exists, False if not.
 */
323 324 325 326 327 328 329 330 331 332 333 334 335
function scrobbleExists($userid, $artist, $track, $time) {
	global $adodb;

	$query = 'SELECT time FROM Scrobbles WHERE userid=? AND artist=? AND track=? AND time=?';
	$params = array($userid, $artist, $track, $time);
	$res = $adodb->GetOne($query, $params);

	if (!$res) {
		return false;
	} else {
		return true;
	}
}
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 391 392 393 394 395 396

/**
 * Sends a scrobble on to any other services the user has connected to their account
 *
 * @todo copied from gnukebox/scrobble-utils.php,
 *		we should review code and see if we can improve, additional params and batch scrobbling would be cool.
 * @todo docs
 */
function forwardScrobble($userid, $artist, $album, $track, $time, $mbid, $source, $rating, $length) {
	global $adodb, $lastfm_key, $lastfm_secret;

	$artist = rawurlencode($artist);
	$track = rawurlencode($track);
	$album = rawurlencode($album);
	$mbid = rawurlencode($mbid);
	$source = rawurlencode($source);
	$rating = rawurlencode($rating);
	$length = rawurlencode($length);

	$res = $adodb->CacheGetAll(600, 'SELECT * FROM Service_Connections WHERE userid = ' . $userid . ' AND forward = 1');
	foreach ($res as &$row) {
		$remote_key = $row['remote_key'];
		$ws_url = $row['webservice_url'];
		$curl_session = curl_init($ws_url);

		$post_vars = '';
		if ($album) {
			$post_vars .= 'album[0]=' . $album . '&';
		}
		$post_vars .= 'api_key=' . $lastfm_key . '&artist[0]=' . $artist;
		if ($length) {
			$post_vars .= '&length[0]=' . $length;
		}
		if ($mbid) {
			$post_vars .= '&mbid[0]=' . $mbid;
		}
		$post_vars .= '&method=track.scrobble';
		if ($rating) {
			$post_vars .= '&rating[0]=' . $rating;
		}
		$post_vars .= '&sk=' . $remote_key;
		if ($source) {
			$post_vars .= '&source[0]='. $source;
		}
		$post_vars .= '&timestamp[0]=' . $time . '&track[0]=' . $track;

		$sig = urldecode(str_replace('&', '', $post_vars));
		$sig = str_replace('=', '', $sig);
		$sig = md5($sig . $lastfm_secret);

		$post_vars .= '&api_sig=' . $sig;
		curl_setopt($curl_session, CURLOPT_POST, true);
		curl_setopt($curl_session, CURLOPT_POSTFIELDS, $post_vars);
		curl_setopt($curl_session, CURLOPT_RETURNTRANSFER, true);
		curl_setopt($curl_session, CURLOPT_CONNECTTIMEOUT, 1);
		curl_setopt($curl_session, CURLOPT_TIMEOUT, 1);
		$response = curl_exec($curl_session);

		curl_close($curl_session);
	}
}