From ab292f7de7a025d82a69b1217adff3c30ae2eaca Mon Sep 17 00:00:00 2001 From: nioc Date: Sun, 23 Aug 2015 00:21:33 +0200 Subject: [PATCH] Upgrade scrobbling API (last.fm and libre.fm) Upgrade the scrobbler class, Lastfm and Librefm plugins to use the new API. Authentication is changed: -admin needs to create a last.fm API account, set their API key and secret in the ampache.cfg file, -users need to grant Ampache application to last.fm / Libre.fm services by using preference tab > plugins. Resolves ampache/ampache#922 Add the "spread the love" function to share your favorites on Last.fm and Libre.fm. Change-Id: Ia81334f4f4506258a27d40b6c257a1e38bd69097 Signed-off-by: nioc --- config/ampache.cfg.php.dist | 5 + lib/class/scrobbler.class.php | 332 +++++++++++++++-------------- lib/class/userflag.class.php | 19 ++ lib/preferences.php | 10 + modules/plugins/Lastfm.plugin.php | 198 ++++++++--------- modules/plugins/Librefm.plugin.php | 198 ++++++++--------- preferences.php | 25 +++ 7 files changed, 413 insertions(+), 374 deletions(-) diff --git a/config/ampache.cfg.php.dist b/config/ampache.cfg.php.dist index c27949aa..d305cd1f 100644 --- a/config/ampache.cfg.php.dist +++ b/config/ampache.cfg.php.dist @@ -477,6 +477,11 @@ art_order = "db,tags,folder,musicbrainz,lastfm,google" ; recommendations and metadata. lastfm_api_key = "d5df942424c71b754e54ce1832505ae2" +; Last.FM API secret +; Set this to your Last.FM api secret to actually use Last.FM for +; scrobbling. +lastfm_api_secret = "" + ; Wanted ; Set this to true to enable display missing albums and the ; possibility for users to mark it as wanted. diff --git a/lib/class/scrobbler.class.php b/lib/class/scrobbler.class.php index b8953a5a..2ffc36a0 100644 --- a/lib/class/scrobbler.class.php +++ b/lib/class/scrobbler.class.php @@ -23,35 +23,90 @@ class scrobbler { public $error_msg; - public $username; - public $password; public $challenge; - public $submit_host; - public $submit_port; - public $submit_url; + public $host; + public $scheme; + public $api_key; public $queued_tracks; - public $reset_handshake = false; - public $scrobble_host = 'post.audioscrobbler.com'; + private $secret; /** * Constructor * This is the constructer it takes a username and password */ - public function __construct($username, $password,$host='',$port='',$url='',$challenge='',$scrobble_host='') + public function __construct($api_key, $scheme='https',$host='',$challenge='', $secret='') { $this->error_msg = ''; - $this->username = trim($username); - $this->password = trim($password); $this->challenge = $challenge; - $this->submit_host = $host; - $this->submit_port = $port; - $this->submit_url = $url; + $this->host = $host; + $this->scheme = $scheme; + $this->api_key = $api_key; + $this->secret=$secret; $this->queued_tracks = array(); - if ($scrobble_host) { - $this->scrobble_host = $scrobble_host; - } } // scrobbler + /** + * get_api_sig + * Provide the API signature for calling Last.fm / Libre.fm services + * It is the md5 of the of all parameter plus API's secret + */ + public function get_api_sig($vars=null) + { + ksort($vars); + $sig = ''; + foreach ($vars as $name => $value) { + $sig .= $name.$value; + } + $sig .= $this->secret; + $sig = md5($sig); + return $sig; + } // get_api_sig + + /** + * call_url + * This is a generic caller for HTTP requests + * It need the method (GET/POST), the url and the parameters + */ + public function call_url($url, $method='GET', $vars=null) + { + // Encode parameters per RFC1738 + $params=http_build_query($vars); + $opts = array( + 'http'=>array( + 'method'=>$method, + 'header'=> array( + 'Host: '.$this->host, + 'User-Agent: Ampache/'.AmpConfig::get('version') + ), + ) + ); + // POST request need parameters in body and additional headers + if ($method == 'POST') { + $opts['http']['content'] = $params; + $opts['http']['header'][] = 'Content-type: application/x-www-form-urlencoded'; + $opts['http']['header'][] = 'Content-length: '.strlen($params); + $params=''; + } + $context = stream_context_create($opts); + if ($params!='') { + // If there are paramters for GET request, adding the "?" caracter before + $params='?'.$params; + } + //debug_event('SCROBBLER', "$this->scheme://$this->host$url$params", 5); + //debug_event('SCROBBLER', serialize($opts), 5); + $fp = fopen("$this->scheme://$this->host$url$params", 'r', false, $context); + if (!$fp) { + return false; + } + ob_start(); + fpassthru($fp); + $buffer = ob_get_contents(); + ob_end_clean(); + fclose($fp); + //debug_event('SCROBBLER', $buffer, 5); + return $buffer; + } // call_url + /** * get_error_msg */ @@ -69,83 +124,50 @@ class scrobbler } // 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 + * get_session_key + * This is a generic caller for HTTP requests + * It need the method (GET/POST), the url and the parameters */ - public function handshake() + public function get_session_key ($token=null) { - $data = array(); - $as_socket = fsockopen($this->scrobble_host, 80, $errno, $errstr, 2); - if (!$as_socket) { - $this->error_msg = $errstr; - return false; + if (!is_null($token)) { + $vars = array( + 'method' => 'auth.getSession', + 'api_key'=> $this->api_key, + 'token' => $token + ); + //sign the call + $sig = $this->get_api_sig($vars); + $vars['api_sig'] = $sig; + //call the getSession API + $response=$this->call_url('/2.0/', 'GET', $vars); + $xml = simplexml_load_string($response); + if ($xml) { + $status = (string) $xml['status']; + if ($status == 'ok') { + if ($xml->session && $xml->session->key) { + return $xml->session->key; + } else { + $this->error_msg = 'Did not receive a valid response'; + return false; + } + } else { + $this->error_msg = $xml->error; + return false; + } + } else { + $this->error_msg = 'Did not receive a valid response'; + return false; + } } - - $username = rawurlencode($this->username); - $timestamp = time(); - $auth_token = rawurlencode(md5($this->password . $timestamp)); - - $get_string = "GET /?hs=true&p=1.2&c=apa&v=0.1&u=$username&t=$timestamp&a=$auth_token HTTP/1.1\r\n"; - - fwrite($as_socket, $get_string); - fwrite($as_socket, "Host: $this->scrobble_host\r\n"); - fwrite($as_socket, "Accept: */*\r\n\r\n"); - - $buffer = ''; - while (!feof($as_socket)) { - $buffer .= fread($as_socket, 4096); - } - 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]); - - // Handle the fact Libre.FM has extranious values at the start of it's handshake response - if (is_numeric(trim($response['0']))) { - array_shift($response); - debug_event('SCROBBLER','Junk in handshake, removing first line',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,7) == 'BADTIME') { - $this->error_msg = 'Your time is too far off from the server, or your PHP timezone is incorrect'; - 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:\/\/([^\/]+)\/(.*)$/', $response[3], $matches)) { - $host_parts = explode(":",$matches[1]); - $data['submit_host'] = $host_parts[0]; - $data['submit_port'] = $host_parts[1] ? $host_parts[1] : '80'; - $data['submit_url'] = '/' . $matches[2]; - } else { - $this->error_msg = "Invalid POST URL returned, unable to continue. Sent:\n$get_string\n----\nReceived:\n" . $buffer . - "\n---------\nExpected:" . print_r($response, true); - return false; - } - - // Remove any extra junk around the challenge - $data['challenge'] = trim($response[1]); - return $data; - } // handshake + $this->error_msg = 'Need a token to call getSession'; + return false; + } // get_session_key /** * 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 + * This queues the LastFM / Libre.fm track by storing it in this object, it doesn't actually + * submit the track or talk to LastFM / Libre in anyway, kind of useless for our uses but its * here, and that's how it is. */ public function queue_track($artist, $album, $title, $timestamp, $length,$track) @@ -169,8 +191,8 @@ class scrobbler /** * 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 + * This actually talks to LastFM / Libre.fm submiting the tracks that are queued up. + * It passed the API key, session key combinted with the signature */ public function submit_tracks() { @@ -183,84 +205,78 @@ class scrobbler //sort array by timestamp ksort($this->queued_tracks); - // build the query string - $query_str = 's='.rawurlencode($this->challenge).'&'; - + // Build the query string (encoded per RFC1738 by the call method) $i = 0; - + $vars= array(); foreach ($this->queued_tracks as $track) { - $query_str .= "a[$i]=".rawurlencode($track['artist'])."&t[$i]=".rawurlencode($track['title'])."&b[$i]=".rawurlencode($track['album'])."&"; - $query_str .= "m[$i]=&l[$i]=".rawurlencode($track['length'])."&i[$i]=".rawurlencode($track['time'])."&"; - $query_str .= "n[$i]=" . rawurlencode($track['track']) . "&o[$i]=P&r[$i]=&"; + //construct array of parameters for each song + $vars["artist[$i]"] = $track['artist']; + $vars["track[$i]"] = $track['title']; + $vars["timestamp[$i]"] = $track['time']; + $vars["album[$i]"] = $track['album']; + $vars["trackNumber[$i]"] = $track['track']; + $vars["duration[$i]"] = $track['length']; $i++; } + // Add the method, API and session keys + $vars['method'] = 'track.scrobble'; + $vars['api_key'] = $this->api_key; + $vars['sk'] = $this->challenge; - if (!trim($this->submit_host) || !$this->submit_port) { - $this->reset_handshake = true; - return false; - } + // Sign the call + $sig = $this->get_api_sig($vars); + $vars['api_sig'] = $sig; - $as_socket = fsockopen($this->submit_host, intval($this->submit_port), $errno, $errstr, 2); - - if (!$as_socket) { - $this->error_msg = $errstr; - $this->reset_handshake = true; - 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, "User-Agent: Ampache/3.6\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"); - // Allow us to debug this - debug_event('SCROBBLER','Query String:' . $query_str,6); - - $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])) { + // Call the method and parse response + $response=$this->call_url('/2.0/', 'POST', $vars); + $xml = simplexml_load_string($response); + if ($xml) { + $status = (string) $xml['status']; + if ($status == 'ok') { + return true; + } else { + $this->error_msg = $xml->error; + return false; + } + } else { $this->error_msg = 'Did not receive a valid response'; - $this->reset_handshake = true; return false; } - $response = explode("\n", $split_response[1]); - if (!isset($response[0])) { - $this->error_msg = 'Unknown error submitting tracks'. - "\nDebug output:\n".$buffer; - $this->reset_handshake = true; - return false; - } - if (substr($response[0], 0, 6) == 'FAILED') { - $this->error_msg = $response[0]; - $this->reset_handshake = true; - return false; - } - if (substr($response[0], 0, 7) == 'BADAUTH') { - $this->error_msg = 'Invalid username/password (' . trim($response[0]) . ')'; - return false; - } - if (substr($response[0],0,10) == 'BADSESSION') { - $this->error_msg = 'Invalid Session passed (' . trim($response[0]) . ')'; - $this->reset_handshake = true; - return false; - } - if (substr($response[0], 0, 2) != 'OK') { - $this->error_msg = 'Response Not ok, unknown error'. - "\nDebug output:\n".$buffer; - $this->reset_handshake = true; - return false; - } - - return true; } // submit_tracks + + /** + * love + * This takes care of spreading your love to the world + * It passed the API key, session key combinted with the signature + */ + public function love($is_loved, $type, $artist = '', $title = '', $album = '') + { + $vars['track'] = $title; + $vars['artist'] = $artist; + // Add the method, API and session keys + $vars['method'] = $is_loved ? 'track.love' : 'track.unlove'; + $vars['api_key'] = $this->api_key; + $vars['sk'] = $this->challenge; + + // Sign the call + $sig = $this->get_api_sig($vars); + $vars['api_sig'] = $sig; + + // Call the method and parse response + $response=$this->call_url('/2.0/', 'POST', $vars); + $xml = simplexml_load_string($response); + if ($xml) { + $status = (string) $xml['status']; + if ($status == 'ok') { + return true; + } else { + $this->error_msg = $xml->error; + return false; + } + } else { + $this->error_msg = 'Did not receive a valid response'; + return false; + } + } // love } // end audioscrobbler class diff --git a/lib/class/userflag.class.php b/lib/class/userflag.class.php index 04f0e7c6..9941c96e 100644 --- a/lib/class/userflag.class.php +++ b/lib/class/userflag.class.php @@ -161,6 +161,25 @@ class Userflag extends database_object parent::add_to_cache('userflag_' . $this->type . '_user' . $user_id, $this->id, $flagged); + // Forward flag to last.fm and Libre.fm (song only) + if ($this->type == 'song') { + $user = new User($user_id); + $song = new Song($this->id); + if ($song) { + $song->format(); + foreach (Plugin::get_plugins('save_mediaplay') as $plugin_name) { + try { + $plugin = new Plugin($plugin_name); + if ($plugin->load($user)) { + $plugin->_plugin->set_flag($song, $flagged); + } + } catch (Exception $e) { + debug_event('user.class.php', 'Stats plugin error: ' . $e->getMessage(), '1'); + } + } + } + } + return true; } // set_flag diff --git a/lib/preferences.php b/lib/preferences.php index aa4aa458..cc5532a6 100644 --- a/lib/preferences.php +++ b/lib/preferences.php @@ -365,6 +365,16 @@ function create_preference_input($name,$value) echo "\t\n"; echo "\n"; break; + case 'lastfm_grant_link': + case 'librefm_grant_link': + // construct links for granting access Ampache application to Last.fm and Libre.fm + $plugin_name = ucfirst(str_replace('_grant_link', '', $name)); + $plugin = new Plugin($plugin_name); + $url = $plugin->_plugin->url; + $api_key = rawurlencode(AmpConfig::get('lastfm_api_key')); + $callback = rawurlencode(AmpConfig::get('web_path').'/preferences.php?tab=plugins&action=grant&plugin='.$plugin_name); + echo "" . UI::get_icon('plugin', T_("Click for grant Ampache to ").$plugin_name).''; + break; default: if (preg_match('/_pass$/', $name)) { echo ''; diff --git a/modules/plugins/Lastfm.plugin.php b/modules/plugins/Lastfm.plugin.php index e505f893..768bfaf4 100644 --- a/modules/plugins/Lastfm.plugin.php +++ b/modules/plugins/Lastfm.plugin.php @@ -20,34 +20,34 @@ * */ -class AmpacheLastfm { - +class AmpacheLastfm +{ public $name = 'Last.FM'; public $categories = 'scrobbling'; public $description = 'Records your played songs to your Last.FM Account'; - public $url = 'http://www.lastfm.com'; - public $version = '000004'; + public $url; + public $version = '000005'; public $min_ampache = '360003'; public $max_ampache = '999999'; // These are internal settings used by this class, run this->load to // fill them out - private $username; - private $password; - private $hostname; - private $port; - private $path; private $challenge; private $user_id; + private $api_key; + private $secret; + private $scheme = 'https'; + private $host = 'www.last.fm'; + private $api_host = 'ws.audioscrobbler.com'; /** * Constructor * This function does nothing... */ - public function __construct() { - + public function __construct() + { + $this->url = $this->scheme.'://'.$this->host; return true; - } // constructor /** @@ -55,20 +55,18 @@ class AmpacheLastfm { * This is a required plugin function. It inserts our preferences * into Ampache */ - public function install() { + public function install() + { // Check and see if it's already installed (they've just hit refresh, those dorks) - if (Preference::exists('lastfm_user')) { return false; } + if (Preference::exists('lastfm_user')) { + return false; + } - Preference::insert('lastfm_user','Last.FM Username','','25','string','plugins'); - Preference::insert('lastfm_md5_pass','Last.FM Password','','25','string','plugins'); - Preference::insert('lastfm_port','Last.FM Submit Port','','25','string','internal'); - Preference::insert('lastfm_host','Last.FM Submit Host','','25','string','internal'); - Preference::insert('lastfm_url','Last.FM Submit URL','','25','string','internal'); Preference::insert('lastfm_challenge','Last.FM Submit Challenge','','25','string','internal'); + Preference::insert('lastfm_grant_link','Last.FM Grant URL','','25','string','plugins'); return true; - } // install /** @@ -76,26 +74,30 @@ class AmpacheLastfm { * This is a required plugin function. It removes our preferences from * the database returning it to its original form */ - public function uninstall() { - - Preference::delete('lastfm_md5_pass'); - Preference::delete('lastfm_user'); - Preference::delete('lastfm_url'); - Preference::delete('lastfm_host'); - Preference::delete('lastfm_port'); + public function uninstall() + { Preference::delete('lastfm_challenge'); - + Preference::delete('lastfm_grant_link'); } // uninstall /** * upgrade * This is a recommended plugin function */ - public function upgrade() { + public function upgrade() + { $from_version = Plugin::get_plugin_version($this->name); if ($from_version < 4) { Preference::rename('lastfm_pass', 'lastfm_md5_pass'); } + if ($from_version < 5) { + Preference::delete('lastfm_md5_pass'); + Preference::delete('lastfm_user'); + Preference::delete('lastfm_url'); + Preference::delete('lastfm_host'); + Preference::delete('lastfm_port'); + Preference::insert('lastfm_grant_link','Last.FM Grant URL','','25','string','plugins'); + } return true; } // upgrade @@ -103,10 +105,19 @@ class AmpacheLastfm { * save_songplay * This takes care of queueing and then submitting the tracks. */ - public function save_mediaplay($song) { + public function save_mediaplay($song) + { // Only support songs - if (strtolower(get_class($song)) != 'song') return false; - + if (strtolower(get_class($song)) != 'song') { + return false; + } + + // Make sure there's actually a session before we keep going + if (!$this->challenge) { + debug_event($this->name,'Session key missing','5'); + return false; + } + // Let's pull the last song submitted by this user $previous = Stats::get_last_song($this->user_id); @@ -123,118 +134,89 @@ class AmpacheLastfm { return false; } - // Make sure there's actually a username and password before we keep going - if (!$this->username || !$this->password) { - debug_event($this->name,'Username or password missing','3'); - return false; - } + // Create our scrobbler and then queue it + $scrobbler = new scrobbler($this->api_key, $this->scheme, $this->api_host, $this->challenge, $this->secret); - // Create our scrobbler with everything this time and then queue it - $scrobbler = new scrobbler($this->username,$this->password,$this->hostname,$this->port,$this->path,$this->challenge); - - // Check to see if the scrobbling works + // Check to see if the scrobbling works by queueing song if (!$scrobbler->queue_track($song->f_artist_full,$song->f_album_full,$song->title,time(),$song->time,$song->track)) { - // Depending on the error we might need to do soemthing here return false; } // Go ahead and submit it now if (!$scrobbler->submit_tracks()) { debug_event($this->name,'Error Submit Failed: ' . $scrobbler->error_msg,'3'); - if ($scrobbler->reset_handshake) { - debug_event($this->name,'Re-running Handshake due to error','3'); - $this->set_handshake($this->user_id); - // Try try again - if ($scrobbler->submit_tracks()) { - debug_event($this->name,'Submission Successful','5'); - return true; - } - } return false; } debug_event($this->name,'Submission Successful','5'); return true; - } // submit /** - * set_handshake - * This runs a handshake and properly updates the preferences as needed. - * It returns the data as an array so we don't have to requery the db. - * This requires a userid so it knows whose crap to update. + * set_flag + * This takes care of spreading your love on Last.fm */ - public function set_handshake($user_id) { - - $scrobbler = new scrobbler($this->username,$this->password); - $data = $scrobbler->handshake(); - - if (!$data) { - debug_event($this->name,'Handshake Failed: ' . $scrobbler->error_msg,'3'); + public function set_flag($song, $flagged) + { + // Make sure there's actually a session before we keep going + if (!$this->challenge) { + debug_event($this->name,'Session key missing','5'); return false; } + // Create our scrobbler and then queue it + $scrobbler = new scrobbler($this->api_key, $this->scheme, $this->api_host, $this->challenge, $this->secret); + if (!$scrobbler->love($flagged, 'song', $song->f_artist_full, $song->title, $song->f_album_full)) { + debug_event($this->name,'Error Love Failed: ' . $scrobbler->error_msg,'3'); + return false; + } + debug_event($this->name,'Sent Love Successfully','5'); + return true; + } // set_flag - $this->hostname = $data['submit_host']; - $this->port = $data['submit_port']; - $this->path = $data['submit_url']; - $this->challenge = $data['challenge']; + /** + * get_session + * This call the getSession method and properly updates the preferences as needed. + * This requires a userid so it knows whose crap to update. + */ + public function get_session($user_id, $token) + { + $scrobbler = new scrobbler($this->api_key, $this->scheme, $this->api_host,'', $this->secret); + $session_key = $scrobbler->get_session_key($token); + if (!$session_key) { + debug_event($this->name,'getSession Failed: ' . $scrobbler->error_msg,'3'); + return false; + } + $this->challenge = $session_key; // Update the preferences - Preference::update('lastfm_port',$user_id,$data['submit_port']); - Preference::update('lastfm_host',$user_id,$data['submit_host']); - Preference::update('lastfm_url',$user_id,$data['submit_url']); - Preference::update('lastfm_challenge',$user_id,$data['challenge']); + Preference::update('lastfm_challenge',$user_id,$session_key); + debug_event($this->name,'getSession Successful','3'); return true; - - } // set_handshake + } // get_session /** * load - * This loads up the data we need into this object, this stuff comes + * This loads up the data we need into this object, this stuff comes * from the preferences. */ - public function load($user) { - + public function load($user) + { + $this->api_key=AmpConfig::get('lastfm_api_key'); + $this->secret=AmpConfig::get('lastfm_api_secret'); $user->set_preferences(); $data = $user->prefs; - - if (strlen(trim($data['lastfm_user']))) { - $this->username = trim($data['lastfm_user']); - } - else { - debug_event($this->name,'No Username, not scrobbling','3'); - return false; - } - if (strlen(trim($data['lastfm_md5_pass']))) { - $this->password = trim($data['lastfm_md5_pass']); - } - else { - debug_event($this->name,'No Password, not scrobbling','3'); - return false; - } - $this->user_id = $user->id; - - // If we don't have the other stuff try to get it before giving up - if (!$data['lastfm_host'] || !$data['lastfm_port'] || !$data['lastfm_url'] || !$data['lastfm_challenge']) { - debug_event($this->name, 'Running Handshake, missing information', '1'); - if (!$this->set_handshake($this->user_id)) { - debug_event($this->name, 'Handshake failed, you lose', '3'); - return false; - } - } - else { - $this->hostname = $data['lastfm_host']; - $this->port = $data['lastfm_port']; - $this->path = $data['lastfm_url']; - $this->challenge = $data['lastfm_challenge']; + // check if user have a session key + if (strlen(trim($data['lastfm_challenge']))) { + $this->challenge= trim($data['lastfm_challenge']); + } else { + debug_event($this->name,'No session key, not scrobbling (need to grant Ampache to last.fm)','5'); + return false; } return true; - } // load - } // end AmpacheLastfm ?> diff --git a/modules/plugins/Librefm.plugin.php b/modules/plugins/Librefm.plugin.php index 997e2702..7bb9f913 100644 --- a/modules/plugins/Librefm.plugin.php +++ b/modules/plugins/Librefm.plugin.php @@ -20,34 +20,34 @@ * */ -class Ampachelibrefm { - +class Ampachelibrefm +{ public $name = 'Libre.FM'; public $categories = 'scrobbling'; public $description = 'Records your played songs to your Libre.FM Account'; - public $url = 'https://libre.fm'; - public $version = '000002'; + public $url; + public $version = '000003'; public $min_ampache = '360003'; public $max_ampache = '999999'; // These are internal settings used by this class, run this->load to // fill them out - private $username; - private $password; - private $hostname; - private $port; - private $path; private $challenge; private $user_id; + private $api_key; + private $secret; + private $scheme = 'https'; + private $host = 'libre.fm'; + private $api_host = 'libre.fm'; /** * Constructor * This function does nothing... */ - public function __construct() { - + public function __construct() + { + $this->url = $this->scheme.'://'.$this->host; return true; - } // constructor /** @@ -55,20 +55,18 @@ class Ampachelibrefm { * This is a required plugin function. It inserts our preferences * into Ampache */ - public function install() { + public function install() + { // Check and see if it's already installed (they've just hit refresh, those dorks) - if (Preference::exists('librefm_user')) { return false; } + if (Preference::exists('librefm_user')) { + return false; + } - Preference::insert('librefm_user','Libre.FM Username','','25','string','plugins'); - Preference::insert('librefm_md5_pass','Libre.FM Password','','25','string','plugins'); - Preference::insert('librefm_port','Libre.FM Submit Port','','25','string','internal'); - Preference::insert('librefm_host','Libre.FM Submit Host','','25','string','internal'); - Preference::insert('librefm_url','Libre.FM Submit URL','','25','string','internal'); Preference::insert('librefm_challenge','Libre.FM Submit Challenge','','25','string','internal'); + Preference::insert('librefm_grant_link','Libre.FM Grant URL','','25','string','plugins'); return true; - } // install /** @@ -76,26 +74,30 @@ class Ampachelibrefm { * This is a required plugin function. It removes our preferences from * the database returning it to its original form */ - public function uninstall() { - - Preference::delete('librefm_md5_pass'); - Preference::delete('librefm_user'); - Preference::delete('librefm_url'); - Preference::delete('librefm_host'); - Preference::delete('librefm_port'); + public function uninstall() + { Preference::delete('librefm_challenge'); - + Preference::delete('librefm_grant_link'); } // uninstall /** * upgrade * This is a recommended plugin function */ - public function upgrade() { + public function upgrade() + { $from_version = Plugin::get_plugin_version($this->name); if ($from_version < 2) { Preference::rename('librefm_pass', 'librefm_md5_pass'); } + if ($from_version < 3) { + Preference::delete('librefm_md5_pass'); + Preference::delete('librefm_user'); + Preference::delete('librefm_url'); + Preference::delete('librefm_host'); + Preference::delete('librefm_port'); + Preference::insert('librefm_grant_link','Libre.FM Grant URL','','25','string','plugins'); + } return true; } // upgrade @@ -103,11 +105,19 @@ class Ampachelibrefm { * save_songplay * This takes care of queueing and then submitting the tracks. */ - public function save_mediaplay($song) { - + public function save_mediaplay($song) + { // Only support songs - if (strtolower(get_class($song)) != 'song') return false; - + if (strtolower(get_class($song)) != 'song') { + return false; + } + + // Make sure there's actually a session before we keep going + if (!$this->challenge) { + debug_event($this->name,'Session key missing','5'); + return false; + } + // Before we start let's pull the last song submitted by this user $previous = Stats::get_last_song($this->user_id); @@ -124,117 +134,89 @@ class Ampachelibrefm { return false; } - // Make sure there's actually a username and password before we keep going - if (!$this->username || !$this->password) { - debug_event($this->name,'Username or password missing','3'); - return false; - } + // Create our scrobbler and then queue it + $scrobbler = new scrobbler($this->api_key, $this->scheme, $this->api_host, $this->challenge, $this->secret); - // Create our scrobbler with everything this time and then queue it - $scrobbler = new scrobbler($this->username,$this->password,$this->hostname,$this->port,$this->path,$this->challenge,'turtle.libre.fm'); - - // Check to see if the scrobbling works + // Check to see if the scrobbling works by queueing song if (!$scrobbler->queue_track($song->f_artist_full,$song->f_album_full,$song->title,time(),$song->time,$song->track)) { - // Depending on the error we might need to do soemthing here return false; } // Go ahead and submit it now if (!$scrobbler->submit_tracks()) { debug_event($this->name,'Error Submit Failed: ' . $scrobbler->error_msg,'3'); - if ($scrobbler->reset_handshake) { - debug_event($this->name, 'Re-running Handshake due to error', '1'); - $this->set_handshake($this->user_id); - // Try try again - if ($scrobbler->submit_tracks()) { - return true; - } - } return false; } debug_event($this->name,'Submission Successful','5'); return true; - } // submit /** - * set_handshake - * This runs a handshake and properly updates the preferences as needed. - * It returns the data as an array so we don't have to requery the db. - * This requires a userid so it knows whose crap to update. + * set_flag + * This takes care of spreading your love on Libre.fm */ - public function set_handshake($user_id) { - - $scrobbler = new scrobbler($this->username,$this->password,'','','','','turtle.libre.fm'); - $data = $scrobbler->handshake(); - - if (!$data) { - debug_event($this->name,'Handshake Failed: ' . $scrobbler->error_msg,'3'); + public function set_flag($song, $flagged) + { + // Make sure there's actually a session before we keep going + if (!$this->challenge) { + debug_event($this->name,'Session key missing','5'); return false; } + // Create our scrobbler and then queue it + $scrobbler = new scrobbler($this->api_key, $this->scheme, $this->api_host, $this->challenge, $this->secret); + if (!$scrobbler->love($flagged, 'song', $song->f_artist_full, $song->title, $song->f_album_full)) { + debug_event($this->name,'Error Love Failed: ' . $scrobbler->error_msg,'3'); + return false; + } + debug_event($this->name,'Sent Love Successfully','5'); + return true; + } // set_flag - $this->hostname = $data['submit_host']; - $this->port = $data['submit_port']; - $this->path = $data['submit_url']; - $this->challenge = $data['challenge']; + /** + * get_session + * This call the getSession method and properly updates the preferences as needed. + * This requires a userid so it knows whose crap to update. + */ + public function get_session($user_id, $token) + { + $scrobbler = new scrobbler($this->api_key, $this->scheme, $this->api_host,'', $this->secret); + $session_key = $scrobbler->get_session_key($token); + if (!$session_key) { + debug_event($this->name,'getSession Failed: ' . $scrobbler->error_msg,'3'); + return false; + } + $this->challenge = $session_key; // Update the preferences - Preference::update('librefm_port',$user_id,$data['submit_port']); - Preference::update('librefm_host',$user_id,$data['submit_host']); - Preference::update('librefm_url',$user_id,$data['submit_url']); - Preference::update('librefm_challenge',$user_id,$data['challenge']); + Preference::update('librefm_challenge',$user_id,$session_key); + debug_event($this->name,'getSession Successful','3'); return true; - - } // set_handshake + } // get_session /** * load - * This loads up the data we need into this object, this stuff comes + * This loads up the data we need into this object, this stuff comes * from the preferences. */ - public function load($user) { - + public function load($user) + { + $this->api_key=AmpConfig::get('lastfm_api_key'); + $this->secret=''; $user->set_preferences(); $data = $user->prefs; - - if (strlen(trim($data['librefm_user']))) { - $this->username = trim($data['librefm_user']); - } - else { - debug_event($this->name,'No Username, not scrobbling','3'); - return false; - } - if (strlen(trim($data['librefm_md5_pass']))) { - $this->password = trim($data['librefm_md5_pass']); - } - else { - debug_event($this->name,'No Password, not scrobbling','3'); - return false; - } - $this->user_id = $user->id; - - // If we don't have the other stuff try to get it before giving up - if (!$data['librefm_host'] || !$data['librefm_port'] || !$data['librefm_url'] || !$data['librefm_challenge']) { - debug_event($this->name,'Running Handshake, missing information','3'); - if (!$this->set_handshake($this->user_id)) { - debug_event($this->name,'Handshake failed, you lose','3'); - return false; - } - } - else { - $this->hostname = $data['librefm_host']; - $this->port = $data['librefm_port']; - $this->path = $data['librefm_url']; - $this->challenge = $data['librefm_challenge']; + // check if user have a session key + if (strlen(trim($data['librefm_challenge']))) { + $this->challenge= trim($data['librefm_challenge']); + } else { + debug_event($this->name,'No session key, not scrobbling (need to grant Ampache to libre.fm)','5'); + return false; } return true; - } // load - } // end Ampachelibrefm ?> diff --git a/preferences.php b/preferences.php index ffd39cf1..e185d8fa 100644 --- a/preferences.php +++ b/preferences.php @@ -148,6 +148,30 @@ switch ($_REQUEST['action']) { $notification_text = T_('User updated successfully'); break; + case 'grant': + // Make sure we're a user and they came from the form + if (!Access::check('interface','25') && $GLOBALS['user']->id > 0) { + UI::access_denied(); + exit; + } + if ($_REQUEST['token'] && in_array($_REQUEST['plugin'], Plugin::get_plugins('save_mediaplay'))) { + // we receive a token for a valid plugin, have to call getSession and obtain a session key + if ($plugin = new Plugin($_REQUEST['plugin'])) { + $plugin->load($GLOBALS['user']); + if ($plugin->_plugin->get_session($GLOBALS['user']->id, $_REQUEST['token'])) { + $title = T_('Updated'); + $text = T_('Your Account has been updated').' : '.$_REQUEST['plugin']; + $next_url = AmpConfig::get('web_path') . '/preferences.php?tab=plugins'; + } else { + $title = T_('Error'); + $text = T_('Your Account has not been updated').' : '.$_REQUEST['plugin']; + $next_url = AmpConfig::get('web_path') . '/preferences.php?tab=plugins'; + } + } + } + $fullname = $GLOBALS['user']->fullname; + $preferences = $GLOBALS['user']->get_preferences($_REQUEST['tab']); + break; default: $fullname = $GLOBALS['user']->fullname; $preferences = $GLOBALS['user']->get_preferences($_REQUEST['tab']); @@ -161,6 +185,7 @@ UI::show_header(); */ switch ($_REQUEST['action']) { case 'confirm': + case 'grant': show_confirmation($title,$text,$next_url,$cancel); break; default: