emms-patches
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [Emms-patches] Last.fm Audioscrobbler


From: Yoni Rabkin
Subject: Re: [Emms-patches] Last.fm Audioscrobbler
Date: Mon, 13 Sep 2010 00:35:51 -0400
User-agent: Gnus/5.13 (Gnus v5.13) Emacs/23.2 (gnu/linux)

The following un-breaks Last.fm streams. Could you check that I didn't
break your stuff as a consequence?

diff --git a/lisp/emms-lastfm-client.el b/lisp/emms-lastfm-client.el
index 7456bc9..6191506 100644
--- a/lisp/emms-lastfm-client.el
+++ b/lisp/emms-lastfm-client.el
@@ -34,6 +34,7 @@
 (require 'emms)
 (require 'emms-source-file)
 (require 'xml)
+(require 'emms-lastfm-scrobbler)
 
 (defvar emms-lastfm-client-username nil
   "Valid Last.fm account username.")
@@ -47,6 +48,12 @@
 (defvar emms-lastfm-client-api-session-key nil
   "Session key for the Last.fm API.")
 
+(defvar emms-lastfm-client-track nil
+  "Latest Last.fm track.")
+
+(defvar emms-lastfm-client-submission-api t
+  "Use the Last.fm submission API if true, otherwise don't.")
+
 (defvar emms-lastfm-client-token nil
   "Authorization token for API.")
 
@@ -80,30 +87,6 @@
 (defvar emms-lastfm-client-playlist-buffer nil
   "Non-interactive Emms Last.fm buffer.")
 
-(defvar emms-lastfm-client-client-identifier "emm"
-  "Client identifier for Emms (Last.fm define this, not us).")
-
-(defvar emms-lastfm-client-submission-protocol-number "1.2.1"
-  "Version of the submissions protocol to which Emms conforms.")
-
-(defvar emms-lastfm-client-published-version "1.0"
-  "Version of this package published to the Last.fm service.")
-
-(defvar emms-lastfm-client-submission-session-id nil
-  "Scrobble session id, for now-playing and submission requests.")
-
-(defvar emms-lastfm-client-submission-now-playing-url nil
-  "URL that should be used for a now-playing request.")
-
-(defvar emms-lastfm-client-submission-url nil
-  "URL that should be used for submissions")
-
-(defvar emms-lastfm-client-track-play-start-timestamp nil
-  "UTC timestamp.")
-
-(defvar emms-lastfm-client-submission-api t
-  "Use the Last.fm submission API if true, otherwise don't.")
-
 (defvar emms-lastfm-client-inhibit-cleanup nil
   "If true, do not perform clean-up after `emms-stop'.")
 
@@ -467,9 +450,6 @@ This function includes the cryptographic signature."
         emms-lastfm-client-playlist-buffer-name))
   (setq emms-playlist-buffer emms-lastfm-client-playlist-buffer))
 
-(defun emms-lastfm-client-timestamp ()
-  "Return a UNIX UTC timestamp."
-  (format-time-string "%s" (current-time) t))
 
 (defun emms-lastfm-client-load-next-track ()
   "Queue the next track from Last.fm."
@@ -481,8 +461,8 @@ This function includes the cryptographic signature."
     (if emms-lastfm-client-playlist
        (let ((track (emms-lastfm-client-consume-next-track)))
          (setq emms-lastfm-client-track track)
-         (setq emms-lastfm-client-track-play-start-timestamp
-               (emms-lastfm-client-timestamp))
+         (setq emms-lastfm-scrobbler-track-play-start-timestamp
+               (emms-lastfm-scrobbler-timestamp))
          (let ((emms-lastfm-client-inhibit-cleanup t))
            (emms-play-url
             (emms-lastfm-client-xspf-get 'location track))))
@@ -492,28 +472,26 @@ This function includes the cryptographic signature."
 (defun emms-lastfm-client-love-track ()
   "Submit the currently playing track with a `love' rating."
   (interactive)
-  (if emms-lastfm-client-track
-      (let ((result (emms-lastfm-client-make-async-submission-call
-                    emms-lastfm-client-track 'love)))
-       ;; the following submission API call looks redundant but
-       ;; isn't; indeed, it might be done away with in a future
-       ;; version of the Last.fm API (see API docs)
-       (emms-lastfm-client-make-call-track-love)
-       (when (equal result 'track-successfully-submitted)
-         (message "track sucessfully submitted with a `love' rating")))
-    (error "no current track")))
+  (when emms-lastfm-client-track
+    (emms-lastfm-scrobbler-make-async-submission-call
+     (emms-lastfm-client-convert-track
+      emms-lastfm-client-track) 'love)
+    ;; the following submission API call looks redundant but
+    ;; isn't; indeed, it might be done away with in a future
+    ;; version of the Last.fm API (see API docs)
+    (emms-lastfm-client-make-call-track-love)))
 
 (defun emms-lastfm-client-ban-track ()
   "Submit currently playing track with a `ban' rating and skip."
   (interactive)
-  (if emms-lastfm-client-track
-      (let ((result (emms-lastfm-client-make-async-submission-call
-                    emms-lastfm-client-track 'ban)))
-       (emms-lastfm-client-make-call-track-ban)
-       (when (equal result 'track-successfully-submitted)
-         (message "track sucessfully submitted with a `ban' rating"))
-       (emms-lastfm-client-load-next-track))
-    (error "no current track")))
+  (when emms-lastfm-client-track
+    (emms-lastfm-scrobbler-make-async-submission-call
+     (emms-lastfm-client-convert-track
+      emms-lastfm-client-track) 'ban)
+    ;; the following submission API call looks redundant but
+    ;; isn't; see `...-love-track'
+    (emms-lastfm-client-make-call-track-ban)
+    (emms-lastfm-client-load-next-track)))
 
 ;; call this `-track-advance' to avoid confusion with Emms'
 ;; `-next-track-' mechanism
@@ -524,10 +502,9 @@ This function includes the cryptographic signature."
               emms-lastfm-client-playlist-buffer)
     (when (and emms-lastfm-client-submission-api
               (not first))
-      (let ((result (emms-lastfm-client-make-async-submission-call
-                    emms-lastfm-client-track nil)))
-       (when (equal result 'track-successfully-submitted)
-         (message "track sucessfully submitted"))))
+      (let ((result (emms-lastfm-scrobbler-make-async-submission-call
+                    (emms-lastfm-client-convert-track
+                     emms-lastfm-client-track) nil)))))
     (emms-lastfm-client-load-next-track)))
 
 (defun emms-lastfm-client-next-function ()
@@ -589,7 +566,7 @@ This function includes the cryptographic signature."
   (emms-lastfm-client-make-call-radio-tune
    (format url username))
   (emms-lastfm-client-make-call-radio-getplaylist)
-  (emms-lastfm-client-handshake)
+  (emms-lastfm-scrobbler-handshake)
   (emms-lastfm-client-play-playlist))
 
 (defun emms-lastfm-client-play-similar-artists (artist)
@@ -601,7 +578,7 @@ This function includes the cryptographic signature."
   (emms-lastfm-client-make-call-radio-tune
    (format "lastfm://artist/%s/similarartists" artist))
   (emms-lastfm-client-make-call-radio-getplaylist)
-  (emms-lastfm-client-handshake)
+  (emms-lastfm-scrobbler-handshake)
   (emms-lastfm-client-play-playlist))
 
 (defun emms-lastfm-client-play-loved ()
@@ -659,10 +636,11 @@ This function includes the cryptographic signature."
     (emms-track-set emms-track 'info-album
                    (emms-lastfm-client-xspf-get 'album track))
     (emms-track-set emms-track 'info-playing-time
-                   (/
-                    (parse-integer
-                     (emms-lastfm-client-xspf-get 'duration track))
-                    1000))
+                   (/ (parse-integer
+                       (emms-lastfm-client-xspf-get 'duration
+                                                    track))
+                      1000))
+    (emms-track-set emms-track 'type 'lastfm-streaming)
     emms-track))
 
 (defun emms-lastfm-client-show-track (track)
@@ -961,182 +939,6 @@ This function includes the cryptographic signature."
   "Function called with DATA after `ban' rating succeeds."
   'track-ban-succeed)
 
-;;; ------------------------------------------------------------------
-;;; Submission API [http://www.last.fm/api/submissions]
-;;; ------------------------------------------------------------------
-
-;; 1.3 Authentication Token for Web Services Authentication: token =
-;; md5(shared_secret + timestamp)
-
-(defun emms-lastfm-client-make-token-for-web-services (timestamp)
-  (when (not (and emms-lastfm-client-api-secret-key timestamp))
-    (error "secret and timestamp needed to make an auth token"))
-  (md5 (concat emms-lastfm-client-api-secret-key timestamp)))
-
-;; Handshake: The initial negotiation with the submissions server to
-;; establish authentication and connection details for the session.
-
-(defun emms-lastfm-client-make-handshake-call ()
-  "Return a submission protocol handshake string."
-  (when (not (and emms-lastfm-client-submission-protocol-number
-                 emms-lastfm-client-client-identifier
-                 emms-lastfm-client-published-version
-                 emms-lastfm-client-username))
-    (error "missing variables to generate handshake call"))
-  (let ((timestamp (format-time-string "%s")))
-    (concat
-     "http://post.audioscrobbler.com/?hs=true";
-     "&p=" emms-lastfm-client-submission-protocol-number
-     "&c=" emms-lastfm-client-client-identifier
-     "&v=" emms-lastfm-client-published-version
-     "&u=" emms-lastfm-client-username
-     "&t=" timestamp
-     "&a=" (emms-lastfm-client-make-token-for-web-services timestamp)
-     "&api_key=" emms-lastfm-client-api-key
-     "&sk=" emms-lastfm-client-api-session-key)))
-
-(defun emms-lastfm-client-handshake ()
-  "Make handshake call."
-  (if emms-lastfm-client-playlist-valid
-      (let* ((url-request-method "GET"))
-       (let ((response
-              (url-retrieve-synchronously
-               (emms-lastfm-client-make-handshake-call))))
-         (emms-lastfm-client-handle-handshake
-          (with-current-buffer response
-            (buffer-substring-no-properties
-             (point-min) (point-max))))))
-    (error "cannot handshake without initializing the client")))
-
-(defun emms-lastfm-client-handle-handshake (response)
-  (let ((ok200 "HTTP/1.1 200 OK"))
-    (when (not (string= ok200 (substring response 0 15)))
-      (error "server not responding correctly"))
-    (with-temp-buffer
-      (insert response)
-      (goto-char (point-min))
-      (re-search-forward "\n\n")
-      (let ((status (buffer-substring-no-properties
-                    (point-at-bol) (point-at-eol))))
-       (cond ((string= status "OK")
-              (forward-line)
-              (setq emms-lastfm-client-submission-session-id
-                    (buffer-substring-no-properties
-                     (point-at-bol) (point-at-eol)))
-              (forward-line)
-              (setq emms-lastfm-client-submission-now-playing-url
-                    (buffer-substring-no-properties
-                     (point-at-bol) (point-at-eol)))
-              (forward-line)
-              (setq emms-lastfm-client-submission-url
-                    (buffer-substring-no-properties
-                     (point-at-bol) (point-at-eol))))
-             ((string= status "BANNED")
-              (error "this version of Emms has been BANNED"))
-             ((string= status "BADAUTH")
-              (error "bad authentication paramaters to handshake"))
-             ((string= status "BADTIME")
-              (error "handshake timestamp diverges too much"))
-             (t
-              (error "unhandled handshake failure")))))))
-
-(defun emms-lastfm-client-assert-submission-handshake ()
-  (when (not (and emms-lastfm-client-submission-session-id
-                 emms-lastfm-client-submission-now-playing-url
-                 emms-lastfm-client-submission-url))
-    (error "cannot use submission API before handshake")))
-
-(defun emms-lastfm-client-hexify-encode (str)
-  "UTF-8 encode and URL-hexify STR."
-  (url-hexify-string (encode-coding-string str 'utf-8)))
-
-(defun emms-lastfm-client-submission-data (track rating)
-  (emms-lastfm-client-assert-submission-handshake)
-  (setq rating
-       (cond ((equal 'love rating) "L")
-             ((equal 'ban rating) "B")
-             ((equal 'skip rating) "S")
-             (t "")))
-  (concat
-   "s=" (emms-lastfm-client-hexify-encode
-        emms-lastfm-client-submission-session-id)
-   "&a[0]=" (emms-lastfm-client-hexify-encode
-            (emms-lastfm-client-xspf-get 'creator track))
-   "&t[0]=" (emms-lastfm-client-hexify-encode
-            (emms-lastfm-client-xspf-get 'title track))
-   ;; warning: won't extend to submitting multiple tracks
-   "&i[0]=" (emms-lastfm-client-hexify-encode
-            emms-lastfm-client-track-play-start-timestamp)
-   "&o[0]=L" (emms-lastfm-client-hexify-encode
-             (emms-lastfm-client-xspf-get
-              'trackauth
-              (emms-lastfm-client-xspf-extension track)))
-   "&r[0]=" (emms-lastfm-client-hexify-encode rating)
-   "&l[0]=" "" ; empty string to be explicit
-   "&b[0]=" "" ; empty string to be explicit
-   "&n[0]=" "" ; empty string to be explicit
-   "&m[0]=" "" ; empty string to be explicit
-   ))
-
-(defun emms-lastfm-client-handle-submission-response (response track rating)
-  (let ((ok200 "HTTP/1.1 200 OK"))
-    (when (not (string= ok200 (substring response 0 15)))
-      (error "submission server not responding correctly"))
-    (with-temp-buffer
-      (insert response)
-      (goto-char (point-min))
-      (re-search-forward "\n\n")
-      (let ((status (buffer-substring-no-properties
-                    (point-at-bol) (point-at-eol))))
-       (cond ((string= status "OK")
-              ;; From the API docs: This indicates that the
-              ;; submission request was accepted for processing. It
-              ;; does not mean that the submission was valid, but
-              ;; only that the authentication and the form of the
-              ;; submission was validated.
-              (message "successfully submitted %s"
-                       (emms-lastfm-client-xspf-get 'title track)))
-             ((string= status "BADSESSION")
-              (emms-lastfm-client-handshake)
-              (emms-lastfm-client-make-async-submission-call track rating))
-             (t
-              (error "unhandled submission failure")))))))
-
-(defun emms-lastfm-client-submit ()
-  "Submit the current track as having been played."
-  (if emms-lastfm-client-track
-      (emms-lastfm-client-make-async-submission-call
-       emms-lastfm-client-track nil)
-    (error "no current track")))
-
-;;; ------------------------------------------------------------------
-;;; Asynchronous Submission
-;;; ------------------------------------------------------------------
-
-(defun emms-lastfm-client-async-submission-callback (status &optional cbargs)
-  "Pass response of asynchronous submission call to handler."
-  (let ((response (copy-sequence
-                  (buffer-substring-no-properties
-                   (point-min) (point-max)))))
-    (emms-lastfm-client-handle-submission-response
-     response
-     (car cbargs) ; track
-     (cdr cbargs) ; rating
-     )))
-
-(defun emms-lastfm-client-make-async-submission-call (track rating)
-  "Make asynchronous submission call."
-  (if emms-lastfm-client-playlist-valid
-      (let* ((url-request-method "POST")
-            (url-request-data
-             (emms-lastfm-client-submission-data track rating))
-            (url-request-extra-headers
-             `(("Content-type" . "application/x-www-form-urlencoded"))))
-       (url-retrieve emms-lastfm-client-submission-url
-                     #'emms-lastfm-client-async-submission-callback
-                     (list (cons track rating))))
-    (error "cannot make submission call without initializing the client")))
-
 (provide 'emms-lastfm-client)
 
 ;;; emms-lastfm-client.el ends here
diff --git a/lisp/emms-lastfm-scrobbler.el b/lisp/emms-lastfm-scrobbler.el
new file mode 100644
index 0000000..1fb5073
--- /dev/null
+++ b/lisp/emms-lastfm-scrobbler.el
@@ -0,0 +1,363 @@
+;;; emms-lastfm-scrobbler.el --- Last.FM Music API
+
+;; Copyright (C) 2009, 2010  Free Software Foundation, Inc.
+
+;; Authors: Bram van der Kroef <address@hidden>, Yoni Rabkin
+;; <address@hidden>
+
+;; Keywords: emms, lastfm
+
+;; EMMS is free software; you can redistribute it and/or modify it
+;; under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 3, or (at your option)
+;; any later version.
+;;
+;; EMMS 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 General Public
+;; License for more details.
+;;
+;; You should have received a copy of the GNU General Public License
+;; along with EMMS; see the file COPYING.  If not, write to the Free
+;; Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+;; MA 02110-1301, USA.
+
+;;; Code:
+
+;;; ------------------------------------------------------------------
+;;; Submission API [http://www.last.fm/api/submissions]
+;;; ------------------------------------------------------------------
+
+(require 'emms)
+(require 'emms-playing-time)
+
+;; Variables referenced from emms-lastfm-client:
+;;  emms-lastfm-client-username, emms-lastfm-client-api-key,
+;;  emms-lastfm-client-api-secret-key, emms-lastfm-client-api-session-key,
+;;   emms-lastfm-client-track
+;; Functions referenced:
+;;  emms-lastfm-client-xspf-get, emms-lastfm-client-xspf-extension,
+;;  emms-lastfm-client-initialize-session
+;;  emms-lastfm-client-convert-track
+
+(defcustom emms-lastfm-scrobbler-submit-track-types '(file)
+  "Specify what types of tracks to submit to Last.fm.
+The default is to only submit files.
+
+To submit every track to Last.fm, set this to t."
+  :type '(choice (const :tag "All" t)
+                 (set :tag "Types"
+                      (const :tag "Files" file)
+                      (const :tag "URLs" url)
+                      (const :tag "Playlists" playlist)
+                      (const :tag "Streamlists" streamlist)
+                      (const :tag "Last.fm streams" lastfm)))
+  :group 'emms-lastfm)
+
+(defvar emms-lastfm-scrobbler-submission-protocol-number "1.2.1"
+  "Version of the submissions protocol to which Emms conforms.")
+
+(defvar emms-lastfm-scrobbler-published-version "1.0"
+  "Version of this package published to the Last.fm service.")
+
+(defvar emms-lastfm-scrobbler-submission-session-id nil
+  "Scrobble session id, for now-playing and submission requests.")
+
+(defvar emms-lastfm-scrobbler-submission-now-playing-url nil
+  "URL that should be used for a now-playing request.")
+
+(defvar emms-lastfm-scrobbler-submission-url nil
+  "URL that should be used for submissions")
+
+(defvar emms-lastfm-scrobbler-client-identifier "emm"
+  "Client identifier for Emms (Last.fm define this, not us).")
+
+(defvar emms-lastfm-scrobbler-track-play-start-timestamp nil
+  "UTC timestamp.")
+
+;; 1.3 Authentication Token for Web Services Authentication: token =
+;; md5(shared_secret + timestamp)
+
+(defun emms-lastfm-scrobbler-make-token-for-web-services (timestamp)
+  (when (not (and emms-lastfm-client-api-secret-key timestamp))
+    (error "secret and timestamp needed to make an auth token"))
+  (md5 (concat emms-lastfm-client-api-secret-key timestamp)))
+
+;; Handshake: The initial negotiation with the submissions server to
+;; establish authentication and connection details for the session.
+
+(defun emms-lastfm-scrobbler-handshake ()
+  "Make handshake call."
+  (let* ((url-request-method "GET"))
+    (let ((response
+           (url-retrieve-synchronously
+            (emms-lastfm-scrobbler-make-handshake-call))))
+      (emms-lastfm-scrobbler-handle-handshake
+       (with-current-buffer response
+         (buffer-substring-no-properties
+          (point-min) (point-max)))))))
+
+(defun emms-lastfm-scrobbler-make-handshake-call ()
+  "Return a submission protocol handshake string."
+  (when (not (and emms-lastfm-scrobbler-submission-protocol-number
+                 emms-lastfm-scrobbler-client-identifier
+                 emms-lastfm-scrobbler-published-version
+                 emms-lastfm-client-username))
+    (error "missing variables to generate handshake call"))
+  (let ((timestamp (emms-lastfm-scrobbler-timestamp)))
+    (concat
+     "http://post.audioscrobbler.com/?hs=true";
+     "&p=" emms-lastfm-scrobbler-submission-protocol-number
+     "&c=" emms-lastfm-scrobbler-client-identifier
+     "&v=" emms-lastfm-scrobbler-published-version
+     "&u=" emms-lastfm-client-username
+     "&t=" timestamp
+     "&a=" (emms-lastfm-scrobbler-make-token-for-web-services timestamp)
+     "&api_key=" emms-lastfm-client-api-key
+     "&sk=" emms-lastfm-client-api-session-key)))
+
+(defun emms-lastfm-scrobbler-handle-handshake (response)
+  (let ((ok200 "HTTP/1.1 200 OK"))
+    (when (not (string= ok200 (substring response 0 15)))
+      (error "server not responding correctly"))
+    (with-temp-buffer
+      (insert response)
+      (goto-char (point-min))
+      (re-search-forward "\n\n")
+      (let ((status (buffer-substring-no-properties
+                    (point-at-bol) (point-at-eol))))
+       (cond ((string= status "OK")
+              (forward-line)
+              (setq emms-lastfm-scrobbler-submission-session-id
+                    (buffer-substring-no-properties
+                     (point-at-bol) (point-at-eol)))
+              (forward-line)
+              (setq emms-lastfm-scrobbler-submission-now-playing-url
+                    (buffer-substring-no-properties
+                     (point-at-bol) (point-at-eol)))
+              (forward-line)
+              (setq emms-lastfm-scrobbler-submission-url
+                    (buffer-substring-no-properties
+                     (point-at-bol) (point-at-eol))))
+             ((string= status "BANNED")
+              (error "this version of Emms has been BANNED"))
+             ((string= status "BADAUTH")
+              (error "bad authentication paramaters to handshake"))
+             ((string= status "BADTIME")
+              (error "handshake timestamp diverges too much"))
+             (t
+              (error "unhandled handshake failure")))))))
+
+(defun emms-lastfm-scrobbler-assert-submission-handshake ()
+  (when (not (and emms-lastfm-scrobbler-submission-session-id
+                 emms-lastfm-scrobbler-submission-now-playing-url
+                 emms-lastfm-scrobbler-submission-url))
+    (error "cannot use submission API before handshake")))
+
+(defun emms-lastfm-scrobbler-hexify-encode (str)
+  "UTF-8 encode and URL-hexify STR."
+  (url-hexify-string (encode-coding-string str 'utf-8)))
+
+(defun emms-lastfm-scrobbler-timestamp ()
+  "Return a UNIX UTC timestamp."
+  (format-time-string "%s"))
+
+(defun emms-lastfm-scrobbler-get-response-status ()
+  "Check the http header and return the body"
+  (let ((ok200 "HTTP/1.1 200 OK"))
+    (when (not (string= ok200 (buffer-substring-no-properties (point-min) 16)))
+      (error "submission server not responding correctly"))
+    (goto-char (point-min))
+    (re-search-forward "\n\n")
+    (buffer-substring-no-properties
+     (point-at-bol) (point-at-eol))))
+
+(defun emms-lastfm-scrobbler-submission-data (track rating)
+  "Format the url parameters containing the track artist, title, rating, time 
the
+  track was played, etc."
+  (emms-lastfm-scrobbler-assert-submission-handshake)
+  (setq rating
+       (cond ((equal 'love rating) "L")
+             ((equal 'ban rating)  "B")
+             ((equal 'skip rating) "S")
+             (t "")))
+  (let ((artist (emms-track-get track 'info-artist))
+        (title  (emms-track-get track 'info-title))
+        (album  (or (emms-track-get track 'info-album) ""))
+        (track-number (emms-track-get track 'info-tracknumber))
+        (musicbrainz-id "")
+        (track-length (number-to-string
+                       (or (emms-track-get track
+                                           'info-playing-time)
+                           0))))
+    (if (and artist title)
+        (concat
+         "s=" (emms-lastfm-scrobbler-hexify-encode
+               emms-lastfm-scrobbler-submission-session-id)
+         "&a[0]=" (emms-lastfm-scrobbler-hexify-encode artist)
+         "&t[0]=" (emms-lastfm-scrobbler-hexify-encode title)
+         "&i[0]=" (emms-lastfm-scrobbler-hexify-encode
+                   emms-lastfm-scrobbler-track-play-start-timestamp)
+         "&o[0]=" (if (equal (emms-track-type track) 
+                            'lastfm-streaming)
+                     (concat "L"
+                             (emms-lastfm-scrobbler-hexify-encode
+                              (emms-lastfm-client-xspf-get
+                               'trackauth
+                               (emms-lastfm-client-xspf-extension
+                                emms-lastfm-client-track))))
+                    "P")
+         "&r[0]=" (emms-lastfm-scrobbler-hexify-encode rating)
+         "&l[0]=" track-length
+         "&b[0]=" (emms-lastfm-scrobbler-hexify-encode album)
+         "&n[0]=" track-number
+         "&m[0]=" musicbrainz-id)
+      (error "Track title and artist must be known."))))
+
+(defun emms-lastfm-scrobbler-nowplaying-data (track)
+  "Format the parameters for the Now playing submission."
+  (let ((artist (emms-track-get track 'info-artist))
+        (title  (emms-track-get track 'info-title))
+        (album  (or (emms-track-get track 'info-album) ""))
+        (track-number (emms-track-get track
+                                      'info-tracknumber))
+        (musicbrainz-id "")
+        (track-length (number-to-string
+                       (or (emms-track-get track
+                                           'info-playing-time)
+                           0))))
+    (if (and artist title)
+        (concat
+         "s=" (emms-lastfm-scrobbler-hexify-encode
+               emms-lastfm-scrobbler-submission-session-id)
+         "&a=" (emms-lastfm-scrobbler-hexify-encode artist)
+         "&t=" (emms-lastfm-scrobbler-hexify-encode title)
+         "&b=" (emms-lastfm-scrobbler-hexify-encode album)
+         "&l=" track-length
+         "&n=" track-number
+         "&m=" musicbrainz-id)
+      (error "Track title and artist must be known."))))
+
+(defun emms-lastfm-scrobbler-allowed-track-type (track)
+  "Check if the track-type is one of the allowed types"
+  (let ((track-type (emms-track-type track)))
+    (or (eq emms-lastfm-scrobbler-submit-track-types t)
+        (and (listp emms-lastfm-scrobbler-submit-track-types)
+             (memq track-type emms-lastfm-scrobbler-submit-track-types)))))
+
+;;; ------------------------------------------------------------------
+;;; EMMS hooks
+;;; ------------------------------------------------------------------
+
+(defun emms-lastfm-scrobbler-start-hook ()
+  "Update the now playing info displayed on the user's last.fm page.  This
+  doesn't affect the user's profile, so it con be done even for tracks that
+  should not be submitted."
+  ;; wait 5 seconds for the stop hook to submit the last track
+  (dotimes (i 5)
+    (sit-for 1))
+  (let ((current-track (emms-playlist-current-selected-track)))
+    (setq emms-lastfm-scrobbler-track-play-start-timestamp
+          (emms-lastfm-scrobbler-timestamp))
+    (if (emms-lastfm-scrobbler-allowed-track-type current-track)
+        (emms-lastfm-scrobbler-make-async-nowplaying-call
+         current-track))))
+
+(defun emms-lastfm-scrobbler-stop-hook ()
+  "Submit the track to last.fm if it has been played for 240
+seconds or half the length of the track."
+  (let ((current-track (emms-playlist-current-selected-track)))
+    (let ((track-length (emms-track-get current-track 'info-playing-time)))
+      (when (and track-length
+                (emms-lastfm-scrobbler-allowed-track-type current-track))
+       (when (and
+              ;; track must be longer than 30 secs
+              (> track-length 30)
+              ;; track must be played for more than 240 secs or
+              ;;   half the tracks length, whichever comes first.
+              (> emms-playing-time (min 240 (/ track-length 2))))
+         (emms-lastfm-scrobbler-make-async-submission-call
+          current-track nil))))))
+
+(defun emms-lastfm-scrobbler-enable ()
+  "Enable the Last.fm scrobbler and submit the tracks EMMS plays
+to last.fm"
+  (interactive)
+  (emms-lastfm-client-initialize-session)
+  (add-hook 'emms-player-started-hook
+           'emms-lastfm-scrobbler-start-hook)
+  (add-hook 'emms-player-stopped-hook
+           'emms-lastfm-scrobbler-stop-hook)
+  (add-hook 'emms-player-finished-hook
+           'emms-lastfm-scrobbler-stop-hook))
+
+(defun emms-lastfm-scrobbler-disable ()
+  "Stop submitting to last.fm"
+  (interactive)
+  (remove-hook 'emms-player-started-hook
+              'emms-lastfm-scrobbler-start-hook)
+  (remove-hook 'emms-player-stopped-hook
+              'emms-lastfm-scrobbler-stop-hook)
+  (remove-hook 'emms-player-finished-hook
+              'emms-lastfm-scrobbler-stop-hook))
+
+;;; ------------------------------------------------------------------
+;;; Asynchronous Submission
+;;; ------------------------------------------------------------------
+
+
+(defun emms-lastfm-scrobbler-make-async-submission-call (track rating)
+  "Make asynchronous submission call."
+  (let ((flarb (emms-lastfm-scrobbler-submission-data track rating)))
+    (setq flooz flarb)
+    (let* ((url-request-method "POST")
+          (url-request-data flarb)
+          (url-request-extra-headers
+           `(("Content-type" . "application/x-www-form-urlencoded"))))
+      (url-retrieve emms-lastfm-scrobbler-submission-url
+                   #'emms-lastfm-scrobbler-async-submission-callback
+                   (list (cons track rating))))))
+
+(defun emms-lastfm-scrobbler-async-submission-callback (status &optional 
cbargs)
+  "Pass response of asynchronous submission call to handler."
+  (let ((response (emms-lastfm-scrobbler-get-response-status)))
+    ;; From the API docs: This indicates that the
+    ;; submission request was accepted for processing. It
+    ;; does not mean that the submission was valid, but
+    ;; only that the authentication and the form of the
+    ;; submission was validated.
+    (let ((track (car cbargs)))
+      (cond ((string= response "OK")
+            (message "Last.fm: Submitted %s"
+                     (emms-track-get track 'info-title)))
+           ((string= response "BADSESSION")
+            (emms-lastfm-scrobbler-handshake)
+            (emms-lastfm-scrobbler-make-async-submission-call (car cbargs) 
(cdr cbargs)))
+           (t
+            (error "unhandled submission failure"))))))
+
+(defun emms-lastfm-scrobbler-make-async-nowplaying-call (track)
+  "Make asynchronous now-playing submission call."
+  (emms-lastfm-scrobbler-assert-submission-handshake)
+  (let* ((url-request-method "POST")
+        (url-request-data
+         (emms-lastfm-scrobbler-nowplaying-data track))
+        (url-request-extra-headers
+         `(("Content-type" . "application/x-www-form-urlencoded"))))
+    (url-retrieve emms-lastfm-scrobbler-submission-now-playing-url
+                 #'emms-lastfm-scrobbler-async-nowplaying-callback
+                 (list (cons track nil)))))
+
+(defun emms-lastfm-scrobbler-async-nowplaying-callback (status &optional 
cbargs)
+  "Pass response of asynchronous now-playing submission call to handler."
+  (let ((response (emms-lastfm-scrobbler-get-response-status)))
+    (cond ((string= response "OK") nil)
+         ((string= response "BADSESSION")
+          (emms-lastfm-scrobbler-handshake)
+          (emms-lastfm-scrobbler-make-async-nowplaying-call (car cbargs)))
+         (t
+          (error "unhandled submission failure")))))
+
+(provide 'emms-lastfm-scrobbler)
+
+;;; emms-lastfm-scrobbler.el ends here.
-- 
   "Cut your own wood and it will warm you twice"

reply via email to

[Prev in Thread] Current Thread [Next in Thread]