From 7c661ba685287efd21512f9f0203641200bffed2 Mon Sep 17 00:00:00 2001 From: Karl 'vollmerk' Vollmer Date: Sun, 17 Dec 2006 08:41:21 +0000 Subject: * Added Recommendations based on matching ratings between users * Integrated LastFM plugin (defaults to disabled still) * Fixed a view issue with the Admin Localplay Level preference * removed some old MPD pages that were not being usedwq --- lib/class/audioscrobbler.class.php | 228 +++++++++++++++++++++++++++++++++++++ lib/class/user.class.php | 140 ++++++++++++++++++++++- lib/init.php | 3 + lib/preferences.php | 24 ++-- 4 files changed, 377 insertions(+), 18 deletions(-) create mode 100644 lib/class/audioscrobbler.class.php (limited to 'lib') diff --git a/lib/class/audioscrobbler.class.php b/lib/class/audioscrobbler.class.php new file mode 100644 index 00000000..68162c46 --- /dev/null +++ b/lib/class/audioscrobbler.class.php @@ -0,0 +1,228 @@ +error_msg = ''; + $this->username = trim($username); + $this->password = trim($password); + $this->challenge = ''; + $this->queued_tracks = array(); + + } // scrobbler + + /** + * get_error_msg + */ + function get_error_msg() { + + return $this->error_msg; + + } // get_error_msg + + /** + * get_queue_count + function get_queue_count() { + + return count($this->queued_tracks); + + } // get_queue_count + + /** + * handshake + * This does a handshake with the audioscrobber server it doesn't pass the password, but + * it does pass the username and has a 10 second timeout + */ + function handshake() { + + $as_socket = @fsockopen('post.audioscrobbler.com', 80, $errno, $errstr, 10); + if(!$as_socket) { + $this->error_msg = $errstr; + return false; + } + + $username = rawurlencode($this->username); + + $get_string = "GET /?hs=true&p=1.1&c=m3a&v=0.1&u=$username HTTP/1.1\r\n"; + + fwrite($as_socket, $get_string); + fwrite($as_socket, "Host: post.audioscrobbler.com\r\n"); + fwrite($as_socket, "Accept: */*\r\n\r\n"); + + $buffer = ''; + while(!feof($as_socket)) { + $buffer .= fread($as_socket, 8192); + } + fclose($as_socket); + $split_response = preg_split("/\r\n\r\n/", $buffer); + if(!isset($split_response[1])) { + $this->error_msg = 'Did not receive a valid response'; + return false; + } + $response = explode("\n", $split_response[1]); + if(substr($response[0], 0, 6) == 'FAILED') { + $this->error_msg = substr($response[0], 7); + return false; + } + if(substr($response[0], 0, 7) == 'BADUSER') { + $this->error_msg = 'Invalid Username'; + return false; + } + if(substr($response[0], 0, 6) == 'UPDATE') { + $this->error_msg = 'You need to update your client: '.substr($response[0], 7); + return false; + } + + if(preg_match('/http:\/\/(.*):(\d+)(.*)/', $response[3], $matches)) { + $this->submit_host = $matches[1]; + $this->submit_port = $matches[2]; + $this->submit_url = $matches[3]; + } else { + $this->error_msg = 'Invalid POST URL returned, unable to continue'; + return false; + } + + $this->challenge = $response[2]; + return true; + + } // handshake + + /** + * queue_track + * This queues the LastFM track by storing it in this object, it doesn't actually + * submit the track or talk to LastFM in anyway, kind of useless for our uses but its + * here, and that's how it is. + */ + function queue_track($artist, $album, $track, $timestamp, $length) { + $date = gmdate('Y-m-d H:i:s', $timestamp); + $mydate = date('Y-m-d H:i:s T', $timestamp); + + if($length < 30) { + debug_event('lastfm',"Not queuing track, too short",'5'); + return false; + } + + $newtrack = array(); + $newtrack['artist'] = $artist; + $newtrack['album'] = $album; + $newtrack['track'] = $track; + $newtrack['length'] = $length; + $newtrack['time'] = $date; + + $this->queued_tracks[$timestamp] = $newtrack; + return true; + + } // queue_track + /** + * submit_tracks + * This actually talks to LastFM submiting the tracks that are queued up. It + * passed the md5'd password combinted with the challenge, which is then md5'd + */ + function submit_tracks() { + + // Check and make sure that we've got some queued tracks + if(!count($this->queued_tracks)) { + $this->error_msg = "No tracks to submit"; + return false; + } + + //sort array by timestamp + ksort($this->queued_tracks); + + // build the query string + $query_str = 'u='.rawurlencode($this->username).'&s='.rawurlencode(md5($this->password.$this->challenge)).'&'; + + $i = 0; + + foreach($this->queued_tracks as $track) { + $query_str .= "a[$i]=".rawurlencode($track['artist'])."&t[$i]=".rawurlencode($track['track'])."&b[$i]=".rawurlencode($track['album'])."&"; + $query_str .= "m[$i]=&l[$i]=".rawurlencode($track['length'])."&i[$i]=".rawurlencode($track['time'])."&"; + $i++; + } + + $as_socket = @fsockopen($this->submit_host, $this->submit_port, $errno, $errstr, 10); + + if(!$as_socket) { + $this->error_msg = $errstr; + return false; + } + + $action = "POST ".$this->submit_url." HTTP/1.0\r\n"; + fwrite($as_socket, $action); + fwrite($as_socket, "Host: ".$this->submit_host."\r\n"); + fwrite($as_socket, "Accept: */*\r\n"); + fwrite($as_socket, "Content-type: application/x-www-form-urlencoded\r\n"); + fwrite($as_socket, "Content-length: ".strlen($query_str)."\r\n\r\n"); + + fwrite($as_socket, $query_str."\r\n\r\n"); + + $buffer = ''; + while(!feof($as_socket)) { + $buffer .= fread($as_socket, 8192); + } + fclose($as_socket); + + $split_response = preg_split("/\r\n\r\n/", $buffer); + if(!isset($split_response[1])) { + $this->error_msg = 'Did not receive a valid response'; + return false; + } + $response = explode("\n", $split_response[1]); + if(!isset($response[0])) { + $this->error_msg = 'Unknown error submitting tracks'. + "\nDebug output:\n".$buffer; + return false; + } + if(substr($response[0], 0, 6) == 'FAILED') { + $this->error_msg = $response[0]; + return false; + } + if(substr($response[0], 0, 7) == 'BADAUTH') { + $this->error_msg = 'Invalid username/password'; + return false; + } + if(substr($response[0], 0, 2) != 'OK') { + $this->error_msg = 'Unknown error submitting tracks'. + "\nDebug output:\n".$buffer; + return false; + } + + return true; + + } // submit_tracks + +} // end audioscrobbler class +?> diff --git a/lib/class/user.class.php b/lib/class/user.class.php index fefca81f..f662e7df 100644 --- a/lib/class/user.class.php +++ b/lib/class/user.class.php @@ -156,11 +156,10 @@ class User { } } // get_preferences - /*! - @function get_favorites - @discussion returns an array of your $type - favorites - */ + /** + * get_favorites + * returns an array of your $type favorites + */ function get_favorites($type) { $web_path = conf('web_path'); @@ -209,6 +208,68 @@ class User { } // get_favorites + /** + * get_recommendations + * This returns recommended objects of $type. The recommendations + * are based on voodoo economics,the phase of the moon and my current BAL. + */ + function get_recommendations($type) { + + /* First pull all of your ratings of this type */ + $sql = "SELECT object_id,user_rating FROM ratings " . + "WHERE object_type='" . sql_escape($type) . "' AND user='" . sql_escape($this->id) . "'"; + $db_results = mysql_query($sql,dbh()); + + while ($r = mysql_fetch_assoc($db_results)) { + /* Store the fact that you rated this */ + $key = $r['object_id']; + $ratings[$key] = true; + + /* Build a key'd array of users with this same rating */ + $sql = "SELECT user FROM ratings WHERE object_type='" . sql_escape($type) . "' " . + "AND user !='" . sql_escape($this->id) . "' AND object_id='" . sql_escape($r['object_id']) . "' " . + "AND user_rating ='" . sql_escape($r['user_rating']) . "'"; + $user_results = mysql_query($sql,dbh()); + + while ($user_info = mysql_fetch_assoc($user_results)) { + $key = $user_info['user']; + $users[$key]++; + } + + } // end while + + /* now we've got your ratings, and all users and the # of ratings that match your ratings + * sort the users[$key] array by value and then find things they've rated high (4+) that you + * haven't rated + */ + asort($users); + $recommendations = array(); + + foreach ($users as $user_id=>$score) { + + /* Find everything they've rated at 4+ */ + $sql = "SELECT object_id,user_rating FROM ratings " . + "WHERE user='" . sql_escape($user_id) . "' AND user_rating >='4' AND object_type = '" . sql_escape($type) . "' ORDER BY user_rating DESC"; + $db_results = mysql_query($sql,dbh()); + + while ($r = mysql_fetch_assoc($db_results)) { + $key = $r['object_id']; + if (isset($ratings[$key])) { continue; } + + /* Let's only get 5 total for now */ + if (count($recommendations) > 5) { return $recommendations; } + + $recommendations[$key] = $r['user_rating']; + + } // end while + + + } // end foreach users + + return $recommendations; + + } // get_recommendations + /*! @function is_logged_in @discussion checks to see if $this user is logged in @@ -433,7 +494,7 @@ class User { $song_info = new Song($song_id); //FIXME:: User uid reference $user = $this->uid; - + if (!$song_info->file) { return false; } $stats = new Stats(); @@ -442,6 +503,38 @@ class User { $stats->insert('artist',$song_info->artist,$user); $stats->insert('genre',$song_info->genre,$user); + /** + * Record this play to LastFM + * because it lags like the dickens try twice on everything + */ + if (!empty($this->prefs['lastfm_user']) AND !empty($this->prefs['lastfm_pass'])) { + $song_info->format_song(); + $lastfm = new scrobbler($this->prefs['lastfm_user'],$this->prefs['lastfm_pass']); + /* Attempt handshake */ + $handshake = $lastfm->handshake(); + + /* We failed, try again */ + if (!$handshake) { sleep(1); $handshake = $lastfm->handshake(); } + + if ($handshake) { + if (!$lastfm->queue_track($song_info->f_artist_full,$song_info->f_album_full,$song_info->title,time(),$song_info->time)) { + debug_event('LastFM','Error: Queue Failed: ' . $lastfm->error_msg,'3'); + } + + $submit = $lastfm->submit_tracks(); + + /* Try again if it fails */ + if (!$submit) { sleep(1); $submit = $lastfm->submit_tracks(); } + + if (!$submit) { + debug_event('LastFM','Error Submit Failed: ' . $lastfm->error_msg,'3'); + } + } // if handshake + else { + debug_event('LastFM','Error: Handshake failed with LastFM: ' . $lastfm->error_msg,'3'); + } + } // record to LastFM + } // update_stats /** @@ -582,6 +675,41 @@ class User { } // format_favorites + /** + * format_recommendations + * This takes an array of [object_id] = ratings + * and displays them in a semi-pretty format + */ + function format_recommendations($items,$type) { + + foreach ($items as $object_id=>$rating) { + + switch ($type) { + case 'artist': + $object = new Artist($object_id); + $object->format_artist(); + $name = $object->f_name; + break; + case 'album': + $object = new Album($object_id); + $object->format_album(); + $name = $object->f_name; + break; + case 'song': + $object = new Song($object_id); + $object->format_song(); + $name = $object->f_title; + break; + } // end switch on type + $results[] = "
  • $name -- $rating
    \n
  • "; + + } // end foreach items + + + return $results; + + } // format_recommendations + /** * fix_preferences * this makes sure that the specified user diff --git a/lib/init.php b/lib/init.php index d645f0ae..abf43b39 100644 --- a/lib/init.php +++ b/lib/init.php @@ -172,6 +172,7 @@ if (conf('ratings')) { require_once(conf('prefix') . '/lib/rating.lib.php'); } + // Classes require_once(conf('prefix') . '/lib/class/localplay.class.php'); require_once(conf('prefix') . '/lib/class/plugin.class.php'); @@ -190,6 +191,8 @@ require_once(conf('prefix') . '/lib/class/access.class.php'); require_once(conf('prefix') . '/lib/class/error.class.php'); require_once(conf('prefix') . '/lib/class/genre.class.php'); require_once(conf('prefix') . '/lib/class/flag.class.php'); +require_once(conf('prefix') . '/lib/class/audioscrobbler.class.php'); + /* Set a new Error Handler */ $old_error_handler = set_error_handler("ampache_error_handler"); diff --git a/lib/preferences.php b/lib/preferences.php index 6d1d5a5a..62781048 100644 --- a/lib/preferences.php +++ b/lib/preferences.php @@ -123,10 +123,16 @@ function update_preferences($pref_id=0) { case 'sample_rate': $value = validate_bitrate($value); break; + /* MD5 the LastFM so it's not plainTXT */ + case 'lastfm_pass': + /* If it's our default blanking thing then don't use it */ + if ($value == '******') { unset($_REQUEST[$name]); break; } + $value = md5($value); + break; default: break; } - + /* Run the update for this preference only if it's set */ if (isset($_REQUEST[$name])) { update_preference($pref_id,$name,$id,$value); @@ -221,10 +227,6 @@ function create_preference_input($name,$value) { elseif ($value == '0') { echo "Disabled"; } - elseif ($name == 'upload_dir' || $name == 'quarantine_dir') { - /* Show Nothing */ - echo " "; - } else { echo $value; } @@ -318,8 +320,8 @@ function create_preference_input($name,$value) { echo "\n"; break; case 'localplay_level': - if ($GLOBALS['user']->prefs['localplay_level'] == '2') { $is_full = 'selected="selected"'; } - elseif ($GLOBALS['user']->prefs['localplay_level'] == '1') { $is_global = 'selected="selected"'; } + if ($value == '2') { $is_full = 'selected="selected"'; } + elseif ($value == '1') { $is_global = 'selected="selected"'; } echo "\n"; break; - case 'quarantine_dir': - case 'upload_dir': - if (!$GLOBALS['user']->has_access(100)) { - break; - } + case 'lastfm_pass': + echo ""; + break; default: echo ""; break; -- cgit