Good day,

I noticed that EMMS wasn't submitting my songs to last.fm anymore. Most of
the code for submitting tracks was already there so I added
emms-lastfm-scrobbler.el to add hooks to EMMS and submit the track info.
I wanted to add a changelog entry but could not find the file. I hope
that's ok.

Bram

>From a492ddead250ca4d306d64f1451a1f5f8ea1318f Mon Sep 17 00:00:00 2001
From: Bram <[email protected]>
Date: Thu, 9 Sep 2010 16:07:11 -0500
Subject: [PATCH] * emms-lastfm-client.el  I updated the Last.fm client so it can submit local tracks
 	and also submit the track that is currently playing. I could not
 	test streaming because I'm not a subscriber and I used up my first
 	free trial period.

* emms-lastfm-scrobbler.el The audioscrobbler has been moved to a separate
	file. When I started submitting my tracks I noticed that they
	showed on last.fm as being played 6 hours in the future, so I
	updated the unix timestamp function to return a UTC timestamp and
	not add the timezone difference.
---
 lisp/emms-lastfm-client.el    |  233 ++------------------------
 lisp/emms-lastfm-scrobbler.el |  364 +++++++++++++++++++++++++++++++++++++++++
 2 files changed, 382 insertions(+), 215 deletions(-)
 create mode 100644 lisp/emms-lastfm-scrobbler.el

diff --git a/lisp/emms-lastfm-client.el b/lisp/emms-lastfm-client.el
index 7456bc9..381d250 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.")
 
@@ -68,9 +75,6 @@
 (defvar emms-lastfm-client-playlist nil
   "Latest Last.fm playlist.")
 
-(defvar emms-lastfm-client-track nil
-  "Latest Last.fm track.")
-
 (defvar emms-lastfm-client-original-next-function nil
   "Original `-next-function'.")
 
@@ -80,30 +84,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 +447,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 +458,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))))
@@ -493,8 +470,9 @@ This function includes the cryptographic signature."
   "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)))
+      (let ((result (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)
@@ -507,8 +485,9 @@ This function includes the cryptographic signature."
   "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)))
+      (let ((result (emms-lastfm-scrobbler-make-async-submission-call
+		     (emms-lastfm-client-convert-track
+                      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"))
@@ -524,7 +503,7 @@ 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
+      (let ((result (emms-lastfm-scrobbler-make-async-submission-call
 		     emms-lastfm-client-track nil)))
 	(when (equal result 'track-successfully-submitted)
 	  (message "track sucessfully submitted"))))
@@ -589,7 +568,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 +580,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 ()
@@ -961,182 +940,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..2fa4a05
--- /dev/null
+++ b/lisp/emms-lastfm-scrobbler.el
@@ -0,0 +1,364 @@
+
+;;; ------------------------------------------------------------------
+;;; 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 
+
+(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.
+
+Note that it is not very meaningful to submit playlists,
+streamlists, or Last.fm streams to Last.fm."
+  :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"))
+;;  (format-time-string "%s" (current-time) t))
+
+(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 (eq (emms-track-type track) 'lastfm)
+                      (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))
+         (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* ((url-request-method "POST")
+         (url-request-data
+          (emms-lastfm-scrobbler-submission-data track rating))
+         (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.
+
+    (cond ((string= response "OK")
+           (message "Last.fm: Submitted %s"
+                    (emms-track-get (car cbargs) '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")
+           )
+          ((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)
-- 
1.7.0

_______________________________________________
Emms-patches mailing list
[email protected]
http://lists.gnu.org/mailman/listinfo/emms-patches

Reply via email to