aboutsummaryrefslogtreecommitdiff
path: root/lisp/emms-lastfm.el
diff options
context:
space:
mode:
Diffstat (limited to 'lisp/emms-lastfm.el')
-rw-r--r--lisp/emms-lastfm.el673
1 files changed, 673 insertions, 0 deletions
diff --git a/lisp/emms-lastfm.el b/lisp/emms-lastfm.el
new file mode 100644
index 0000000..1f597d7
--- /dev/null
+++ b/lisp/emms-lastfm.el
@@ -0,0 +1,673 @@
+;;; emms-lastfm.el --- add your listened songs to your profile at last.fm
+
+;; Copyright (C) 2006, 2007 Free Software Foundation, Inc.
+
+;; Author: Tassilo Horn <tassilo@member.fsf.org>
+
+;; Keywords: emms, mp3, mpeg, multimedia
+
+;; This file is part of EMMS.
+
+;; 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.
+
+;;; Commentary:
+
+;; This code sends information about what music you are playing to last.fm.
+;; See <URL:http://www.last.fm> and
+;; <URL:http://www.audioscrobbler.net/wiki/Protocol1.1>.
+
+;;; Sample configuration:
+
+;; (setq emms-lastfm-username "my-user-name"
+;; emms-lastfm-password "very-secret!")
+
+;;; Usage:
+
+;; To activate the last.fm emms plugin, run:
+;; `M-x emms-lastfm-enable'
+
+;; Now all music you listen to will be submitted to Last.fm to enhance your
+;; profile.
+
+;; To deactivate the last.fm emms plugin, run:
+;; `M-x emms-lastfm-disable'
+
+;; Beside submitting the tracks you listen to, you can also listen to Last.fm
+;; radio. Simply copy the lastfm:// URL and run & paste:
+;; `M-x emms-lastfm-radio RET lastfm://artist/Britney Spears/fans'
+;; (Of course you don't need to use _this_ URL. :-))
+
+;; You can also insert Last.fm streams into playlists (or use
+;; emms-streams.el to listen to them) by activating the player as
+;; follows.
+;; (add-to-list 'emms-player-list 'emms-player-lastfm-radio)
+;; To insert a Last.fm stream into a playlist, do
+;; (emms-insert-lastfm "lastfm://rest-of-url")
+
+;; There are some functions for conveniently playing the Similar Artists and
+;; the Global Tag Radio. Here you only need to enter the band's name or the tag
+;; respectively.
+;; `M-x emms-lastfm-radio-similar-artists RET Britney Spears'
+;; `M-x emms-lastfm-radio-global-tag RET pop'
+
+;; When you're listening to a Last.fm radio station you have the possibility to
+;; give feedback to them. If you like the current song, type
+;; `M-x emms-lastfm-radio-love'.
+;; If it's not that good, or it just happens to not fit to your actual mood,
+;; type
+;; `M-x emms-lastfm-radio-skip'
+;; and this song will be skipped.
+;; If you really hate that song and you never want to hear it again, ban it by
+;; typing
+;; `M-x emms-lastfm-radio-ban'.
+
+;;; TODO
+;;
+;; - Get the last.fm radio stuff right again. Currently the rating stuff seems
+;; to be broken. There seems to be no official API, so one needs to look
+;; into the sources of the official client which can be found at
+;; http://www.audioscrobbler.net/development/client/.
+
+;; -----------------------------------------------------------------------
+
+(require 'url)
+(require 'emms)
+(require 'emms-mode-line)
+(require 'emms-playing-time)
+(require 'emms-source-file)
+(require 'emms-url)
+
+;;; Variables
+
+(defgroup emms-lastfm nil
+ "Interaction with the services offered by http://www.last.fm."
+ :prefix "emms-lastfm-"
+ :group 'emms)
+
+(defcustom emms-lastfm-username ""
+ "Your last.fm username"
+ :type 'string
+ :group 'emms-lastfm)
+
+(defcustom emms-lastfm-password ""
+ "Your last.fm password"
+ :type 'string
+ :group 'emms-lastfm)
+
+(defcustom emms-lastfm-submission-verbose-p nil
+ "If non-nil, display a message every time we submit a track to Last.fm."
+ :type 'boolean
+ :group 'emms-lastfm)
+
+(defcustom emms-lastfm-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)
+
+(defconst emms-lastfm-server "http://post.audioscrobbler.com/"
+ "The last.fm server responsible for the handshaking
+procedure. Only for internal use.")
+(defconst emms-lastfm-client-id "ems"
+ "The client ID of EMMS. Don't change it!")
+(defconst emms-lastfm-client-version 0.2
+ "The version registered at last.fm. Don't change it!")
+(defconst emms-lastfm-protocol-version 1.2
+ "The version of the supported last.fm protocol. Don't change it.")
+
+;; used internally
+(defvar emms-lastfm-process nil "-- only used internally --")
+(defvar emms-lastfm-session-id nil "-- only used internally --")
+(defvar emms-lastfm-now-playing-url nil "-- only used internally --")
+(defvar emms-lastfm-submit-url nil "-- only used internally --")
+(defvar emms-lastfm-current-track nil "-- only used internally --")
+(defvar emms-lastfm-timer nil "-- only used internally --")
+(defvar emms-lastfm-current-track-starting-time-string nil "-- only used internally --")
+
+;;; Scrobbling
+
+(defun emms-lastfm-new-track-function ()
+ "This function should run whenever a new track starts (or a
+paused track resumes) and sets the track submission timer."
+ (setq emms-lastfm-current-track
+ (emms-playlist-current-selected-track))
+ (setq emms-lastfm-current-track-starting-time-string
+ (emms-lastfm-current-unix-time-string))
+ ;; Tracks should be submitted, if they played 240 secs or half of their
+ ;; length, whichever comes first.
+ (let ((secs (emms-track-get emms-lastfm-current-track 'info-playing-time))
+ (type (emms-track-type emms-lastfm-current-track)))
+ (when (and secs
+ (or (eq emms-lastfm-submit-track-types t)
+ (and (listp emms-lastfm-submit-track-types)
+ (memq type emms-lastfm-submit-track-types))))
+ (when (> secs 240)
+ (setq secs 240))
+ (unless (< secs 30) ;; Skip titles shorter than 30 seconds
+ (setq secs (- (/ secs 2) emms-playing-time))
+ (unless (< secs 0)
+ (setq emms-lastfm-timer
+ (run-with-timer secs nil 'emms-lastfm-submit-track))))))
+ ;; Update the now playing info displayed on the user's last.fm page. This
+ ;; doesn't affect the user's profile, so it can be done even for tracks that
+ ;; should not be submitted.
+ (emms-lastfm-submit-now-playing))
+
+(defun emms-lastfm-http-POST (url string sentinel &optional sentinel-args)
+ "Perform a HTTP POST request to URL using STRING as data.
+STRING will be encoded to utf8 before the request. Call SENTINEL
+with the result buffer."
+ (let ((url-http-attempt-keepalives nil)
+ (url-show-status emms-lastfm-submission-verbose-p)
+ (url-request-method "POST")
+ (url-request-extra-headers
+ '(("Content-type"
+ . "application/x-www-form-urlencoded; charset=utf-8")))
+ (url-request-data (encode-coding-string string 'utf-8)))
+ (url-retrieve url sentinel sentinel-args)))
+
+(defun emms-lastfm-http-GET (url sentinel &optional sentinel-args)
+ "Perform a HTTP GET request to URL.
+Call SENTINEL with SENTINEL-ARGS and the result buffer."
+ (let ((url-show-status emms-lastfm-submission-verbose-p)
+ (url-request-method "GET"))
+ (url-retrieve url sentinel sentinel-args)))
+
+(defun emms-lastfm-submit-now-playing ()
+ "Submit now-playing infos to last.fm.
+These will be displayed on the user's last.fm page."
+ (let* ((artist (emms-track-get emms-lastfm-current-track 'info-artist))
+ (title (emms-track-get emms-lastfm-current-track 'info-title))
+ (album (emms-track-get emms-lastfm-current-track 'info-album))
+ (track-number (emms-track-get emms-lastfm-current-track
+ 'info-tracknumber))
+ (musicbrainz-id "")
+ (track-length (number-to-string
+ (or (emms-track-get emms-lastfm-current-track
+ 'info-playing-time)
+ 0))))
+ ;; wait up to 5 seconds to submit np infos in order to finish handshaking.
+ (dotimes (i 5)
+ (when (not (and emms-lastfm-session-id
+ emms-lastfm-now-playing-url))
+ (sit-for 1)))
+ (when (and emms-lastfm-session-id
+ emms-lastfm-now-playing-url)
+ (emms-lastfm-http-POST emms-lastfm-now-playing-url
+ (concat "&s=" emms-lastfm-session-id
+ "&a[0]=" (emms-url-quote artist)
+ "&t[0]=" (emms-url-quote title)
+ "&b[0]=" (emms-url-quote album)
+ "&l[0]=" track-length
+ "&n[0]=" track-number
+ "&m[0]=" musicbrainz-id)
+ 'emms-lastfm-submit-now-playing-sentinel))))
+
+(defun emms-lastfm-submit-now-playing-sentinel (&rest args)
+ "Parses the server reponse and inform the user if all worked
+well or if an error occured."
+ (let ((buffer (current-buffer)))
+ (emms-http-decode-buffer buffer)
+ (goto-char (point-min))
+ ;; skip to the first empty line and go one line further. There the last.fm
+ ;; response starts.
+ (re-search-forward "^$" nil t)
+ (forward-line)
+ (if (re-search-forward "^OK$" nil t)
+ (progn
+ (when emms-lastfm-submission-verbose-p
+ (message "EMMS: Now playing infos submitted to last.fm"))
+ (kill-buffer buffer))
+ (message "EMMS: Now playing infos couldn't be submitted to last.fm: %s"
+ (emms-read-line)))))
+
+(defun emms-lastfm-cancel-timer ()
+ "Cancels `emms-lastfm-timer' if it is running."
+ (emms-cancel-timer emms-lastfm-timer)
+ (setq emms-lastfm-timer nil))
+
+(defun emms-lastfm-pause ()
+ "Handles things to be done when the player is paused or
+resumed."
+ (if emms-player-paused-p
+ ;; the player paused
+ (emms-lastfm-cancel-timer)
+ ;; The player resumed
+ (emms-lastfm-new-track-function)))
+
+(defun emms-lastfm (&optional ARG)
+ "Start submitting the tracks you listened to to
+http://www.last.fm, if ARG is positive. If ARG is negative or
+zero submission of the tracks will be stopped. This applies to
+the current track, too."
+ (interactive "p")
+ (cond
+ ((not (and emms-lastfm-username emms-lastfm-password))
+ (message "%s"
+ (concat "EMMS: In order to activate the last.fm plugin you "
+ "first have to set both `emms-lastfm-username' and "
+ "`emms-lastfm-password'")))
+ ((not emms-playing-time-p)
+ (message "%s"
+ (concat "EMMS: The last.fm plugin needs the functionality "
+ "provided by `emms-playing-time'. It seems that you "
+ "disabled it explicitly in your init file using code "
+ "like this: `(emms-playing-time -1)'. Delete that "
+ "line and have a look at `emms-playing-time's doc "
+ "string")))
+ (t
+ (if (and ARG (> ARG 0))
+ (progn
+ ;; Append it. Else the playing time could be started a bit too late.
+ (add-hook 'emms-player-started-hook
+ 'emms-lastfm-handshake-if-needed t)
+ ;; Has to be appended, because it has to run after
+ ;; `emms-playing-time-start'
+ (add-hook 'emms-player-started-hook
+ 'emms-lastfm-new-track-function t)
+ (add-hook 'emms-player-stopped-hook
+ 'emms-lastfm-cancel-timer)
+ (add-hook 'emms-player-paused-hook
+ 'emms-lastfm-pause)
+ (message "EMMS Last.fm plugin activated"))
+ (remove-hook 'emms-player-started-hook
+ 'emms-lastfm-handshake-if-needed)
+ (remove-hook 'emms-player-started-hook
+ 'emms-lastfm-new-track-function)
+ (remove-hook 'emms-player-stopped-hook
+ 'emms-lastfm-cancel-timer)
+ (remove-hook 'emms-player-paused-hook
+ 'emms-lastfm-pause)
+ (when emms-lastfm-timer (emms-cancel-timer emms-lastfm-timer))
+ (setq emms-lastfm-session-id nil
+ emms-lastfm-submit-url nil
+ emms-lastfm-process nil
+ emms-lastfm-current-track nil)
+ (message "EMMS Last.fm plugin deactivated")))))
+
+(defalias 'emms-lastfm-activate 'emms-lastfm)
+(emms-make-obsolete 'emms-lastfm-activate 'emms-lastfm "EMMS 2.2")
+
+(defun emms-lastfm-enable ()
+ "Enable the emms last.fm plugin."
+ (interactive)
+ (emms-lastfm 1))
+
+(defun emms-lastfm-disable ()
+ "Disable the emms last.fm plugin."
+ (interactive)
+ (emms-lastfm -1))
+
+(defun emms-lastfm-restart ()
+ "Disable and reenable the last.fm plugin. This will cause a new
+handshake."
+ (emms-lastfm-disable)
+ (emms-lastfm-enable))
+
+(defun emms-lastfm-handshake-if-needed ()
+ (when (not (and emms-lastfm-session-id
+ emms-lastfm-submit-url
+ emms-lastfm-now-playing-url))
+ (emms-lastfm-handshake)))
+
+(defun emms-lastfm-current-unix-time-string ()
+ (replace-regexp-in-string "\\..*" "" (number-to-string (float-time))))
+
+(defun emms-lastfm-handshake ()
+ "Handshakes with the last.fm server."
+ (let ((timestamp (emms-lastfm-current-unix-time-string)))
+ (emms-lastfm-http-GET
+ (concat emms-lastfm-server
+ "?hs=true"
+ "&p=" (number-to-string emms-lastfm-protocol-version)
+ "&c=" emms-lastfm-client-id
+ "&v=" (number-to-string emms-lastfm-client-version)
+ "&u=" (emms-url-quote emms-lastfm-username)
+ "&t=" timestamp
+ "&a=" (md5 (concat (md5 emms-lastfm-password) timestamp)))
+ 'emms-lastfm-handshake-sentinel)))
+
+(defun emms-lastfm-handshake-sentinel (&rest args)
+ "Parses the server reponse and inform the user if all worked
+well or if an error occured."
+ (let ((buffer (current-buffer)))
+ (emms-http-decode-buffer buffer)
+ (goto-char (point-min))
+ ;; skip to the first empty line and go one line further. There the last.fm
+ ;; response starts.
+ (re-search-forward "^$" nil t)
+ (forward-line)
+ (let ((response (emms-read-line)))
+ (if (not (string-match (rx (or "OK")) response))
+ (message "EMMS: Handshake failed: %s" response)
+ (forward-line)
+ (setq emms-lastfm-session-id (emms-read-line))
+ (forward-line)
+ (setq emms-lastfm-now-playing-url (emms-read-line))
+ (forward-line)
+ (setq emms-lastfm-submit-url (emms-read-line))
+ (message "EMMS: Handshaking with server done")
+ (kill-buffer buffer)))))
+
+(defun emms-lastfm-submit-track ()
+ "Submits the current track (`emms-lastfm-current-track') to
+last.fm."
+ (let* ((artist (emms-track-get emms-lastfm-current-track 'info-artist))
+ (title (emms-track-get emms-lastfm-current-track 'info-title))
+ (album (emms-track-get emms-lastfm-current-track 'info-album))
+ (track-number (emms-track-get emms-lastfm-current-track 'info-tracknumber))
+ (musicbrainz-id "")
+ (track-length (number-to-string
+ (emms-track-get emms-lastfm-current-track
+ 'info-playing-time))))
+ (emms-lastfm-http-POST
+ emms-lastfm-submit-url
+ (concat "&s=" emms-lastfm-session-id
+ "&a[0]=" (emms-url-quote artist)
+ "&t[0]=" (emms-url-quote title)
+ "&i[0]=" emms-lastfm-current-track-starting-time-string
+ "&o[0]=P" ;; TODO: Maybe support others. See the API.
+ "&r[0]=" ;; The rating. Empty if not applicable (for P it's not)
+ "&l[0]=" track-length
+ "&b[0]=" (emms-url-quote album)
+ "&n[0]=" track-number
+ "&m[0]=" musicbrainz-id)
+ 'emms-lastfm-submission-sentinel)))
+
+(defun emms-lastfm-submission-sentinel (&rest args)
+ "Parses the server reponse and inform the user if all worked
+well or if an error occured."
+ (let ((buffer (current-buffer)))
+ (emms-http-decode-buffer buffer)
+ (goto-char (point-min))
+ ;; skip to the first empty line and go one line further. There the last.fm
+ ;; response starts.
+ (re-search-forward "^$" nil t)
+ (forward-line)
+ (if (re-search-forward "^OK$" nil t)
+ (progn
+ (when emms-lastfm-submission-verbose-p
+ (message "EMMS: \"%s\" submitted to last.fm"
+ (emms-track-description emms-lastfm-current-track)))
+ (kill-buffer buffer))
+ (message "EMMS: Song couldn't be submitted to last.fm: %s"
+ (emms-read-line)))))
+
+;;; Playback of lastfm:// streams
+
+(defgroup emms-player-lastfm-radio nil
+ "EMMS player for Last.fm streams."
+ :group 'emms-player
+ :prefix "emms-player-lastfm-")
+
+(defcustom emms-player-lastfm-radio (emms-player 'emms-lastfm-radio-start
+ 'ignore ; no need to stop
+ 'emms-lastfm-radio-playable-p)
+ "*Parameters for the Last.fm radio player."
+ :type '(cons symbol alist)
+ :group 'emms-player-lastfm-radio)
+
+(defconst emms-lastfm-radio-base-url "http://ws.audioscrobbler.com/radio/"
+ "The base URL for playing lastfm:// stream.
+-- only used internally --")
+
+(defvar emms-lastfm-radio-session nil "-- only used internally --")
+(defvar emms-lastfm-radio-stream-url nil "-- only used internally --")
+
+(defun emms-lastfm-radio-get-handshake-url ()
+ (concat emms-lastfm-radio-base-url
+ "handshake.php?version=" (number-to-string
+ emms-lastfm-client-version)
+ "&platform=" emms-lastfm-client-id
+ "&username=" (emms-url-quote emms-lastfm-username)
+ "&passwordmd5=" (md5 emms-lastfm-password)
+ "&debug=" (number-to-string 9)))
+
+(defun emms-lastfm-radio-handshake (fn radio-url)
+ "Handshakes with the last.fm server.
+Calls FN when done with RADIO-URL as its only argument."
+ (emms-lastfm-http-GET (emms-lastfm-radio-get-handshake-url)
+ 'emms-lastfm-radio-handshake-sentinel
+ (list fn radio-url)))
+
+(defun emms-lastfm-radio-handshake-sentinel (status fn radio-url)
+ (let ((buffer (current-buffer)))
+ (emms-http-decode-buffer buffer)
+ (setq emms-lastfm-radio-session (emms-key-value "session"))
+ (setq emms-lastfm-radio-stream-url (emms-key-value "stream_url"))
+ (kill-buffer buffer)
+ (if (and emms-lastfm-radio-session emms-lastfm-radio-stream-url)
+ (progn
+ (message "EMMS: Handshaking for Last.fm playback successful")
+ (funcall fn radio-url))
+ (message "EMMS: Failed handshaking for Last.fm playback"))))
+
+(defun emms-lastfm-radio-1 (lastfm-url)
+ "Internal function used by `emms-lastfm-radio'."
+ (if (and emms-lastfm-radio-session
+ emms-lastfm-radio-stream-url)
+ (progn
+ (emms-lastfm-http-GET
+ (concat emms-lastfm-radio-base-url
+ "adjust.php?"
+ "session=" emms-lastfm-radio-session
+ "&url=" (emms-url-quote lastfm-url)
+ "&debug=" (number-to-string 0))
+ 'emms-lastfm-radio-sentinel))
+ (message "EMMS: Cannot play Last.fm stream")))
+
+(defun emms-lastfm-radio (lastfm-url)
+ "Plays the stream associated with the given Last.fm URL. (A
+Last.fm URL has the form lastfm://foo/bar/baz, e.g.
+
+ lastfm://artist/Manowar/similarartists
+
+or
+
+ lastfm://globaltags/metal."
+ (interactive "sLast.fm URL: ")
+ ;; Streamed songs must not be added to the lastfm profile
+ (emms-lastfm-disable)
+ (if (not (and emms-lastfm-radio-session
+ emms-lastfm-radio-stream-url))
+ (emms-lastfm-radio-handshake #'emms-lastfm-radio-1 lastfm-url)
+ (emms-lastfm-radio-1 lastfm-url)))
+
+(defun emms-lastfm-radio-playable-p (track)
+ "Determine whether the Last.fm player can play this track."
+ (let ((name (emms-track-get track 'name))
+ (type (emms-track-get track 'type)))
+ (and (eq type 'lastfm)
+ (string-match "^lastfm://" name))))
+
+(defun emms-lastfm-radio-start (track)
+ "Start playing TRACK."
+ (when (emms-lastfm-radio-playable-p track)
+ (let ((name (emms-track-get track 'name)))
+ (emms-lastfm-radio name))))
+
+(defcustom emms-lastfm-radio-metadata-period 15
+ "When listening to Last.fm Radio every how many seconds should
+emms-lastfm poll for metadata? If set to nil, there won't be any
+polling at all.
+
+The default is 15: That means that the mode line will display the
+wrong (last) track's data for a maximum of 15 seconds. If your
+network connection has a big latency this value may be too
+high. (But then streaming a 128KHz mp3 won't be fun anyway.)"
+ :type '(choice integer
+ (const :tag "Disable" nil))
+ :group 'emms-lastfm)
+
+(defun emms-lastfm-radio-sentinel (&rest args)
+ (let ((buffer (current-buffer)))
+ (emms-http-decode-buffer buffer)
+ (if (string= (emms-key-value "response" buffer) "OK")
+ (progn
+ (kill-buffer buffer)
+ (emms-play-url emms-lastfm-radio-stream-url)
+ (when emms-lastfm-radio-metadata-period
+ (setq emms-lastfm-timer
+ (run-with-timer 0 emms-lastfm-radio-metadata-period
+ 'emms-lastfm-radio-request-metadata))
+ (add-hook 'emms-player-stopped-hook
+ 'emms-lastfm-cancel-timer))
+ (message "EMMS: Playing Last.fm stream"))
+ (kill-buffer buffer)
+ (message "EMMS: Bad response from Last.fm"))))
+
+(defun emms-lastfm-np (&optional insertp callback)
+ "Show the currently-playing lastfm radio tune.
+
+If INSERTP is non-nil, insert the description into the current
+buffer instead.
+
+If CALLBACK is a function, call it with the current buffer and
+description as arguments instead of displaying the description or
+inserting it."
+ (interactive "P")
+ (emms-lastfm-radio-request-metadata
+ (lambda (status insertp buffer callback)
+ (let ((response-buf (current-buffer))
+ artist title)
+ (emms-http-decode-buffer response-buf)
+ (setq artist (emms-key-value "artist" response-buf)
+ title (emms-key-value "track" response-buf))
+ (kill-buffer response-buf)
+ (let ((msg (if (and title artist)
+ (format emms-show-format
+ (format "%s - %s" artist title))
+ "Nothing playing right now")))
+ (cond ((functionp callback)
+ (when (and title artist)
+ (funcall callback buffer msg)))
+ ((and insertp title artist)
+ (with-current-buffer buffer
+ (insert msg)))
+ (t (message msg))))))
+ (list insertp (current-buffer) callback)))
+
+(defun emms-lastfm-radio-similar-artists (artist)
+ "Plays the similar artist radio of ARTIST."
+ (interactive "sArtist: ")
+ (emms-lastfm-radio (concat "lastfm://artist/"
+ artist
+ "/similarartists")))
+
+(defun emms-lastfm-radio-global-tag (tag)
+ "Plays the global tag radio of TAG."
+ (interactive "sGlobal Tag: ")
+ (emms-lastfm-radio (concat "lastfm://globaltags/" tag)))
+
+(defun emms-lastfm-radio-artist-fan (artist)
+ "Plays the artist fan radio of ARTIST."
+ (interactive "sArtist: ")
+ (emms-lastfm-radio (concat "lastfm://artist/" artist "/fans")))
+
+(defun emms-lastfm-radio-love ()
+ "Inform Last.fm that you love the currently playing song."
+ (interactive)
+ (emms-lastfm-radio-rating "love"))
+
+(defun emms-lastfm-radio-skip ()
+ "Inform Last.fm that you want to skip the currently playing
+song."
+ (interactive)
+ (emms-lastfm-radio-rating "skip"))
+
+(defun emms-lastfm-radio-ban ()
+ "Inform Last.fm that you want to ban the currently playing
+song."
+ (interactive)
+ (emms-lastfm-radio-rating "ban"))
+
+(defun emms-lastfm-radio-rating (command)
+ (emms-lastfm-http-GET
+ (concat emms-lastfm-radio-base-url
+ "control.php?"
+ "session=" emms-lastfm-radio-session
+ "&command=" command
+ "&debug=" (number-to-string 0))
+ 'emms-lastfm-radio-rating-sentinel))
+
+(defun emms-lastfm-radio-rating-sentinel (&rest args)
+ (let ((buffer (current-buffer)))
+ (emms-http-decode-buffer buffer)
+ (if (string= (emms-key-value "response" buffer) "OK")
+ (message "EMMS: Rated current track")
+ (message "EMMS: Rating failed"))
+ (kill-buffer buffer)))
+
+(defun emms-lastfm-radio-request-metadata (&optional fn data)
+ "Request the metadata of the current song and display it.
+
+If FN is given, call it instead of
+`emms-lastfm-radio-request-metadata-sentinel', with DATA as its
+first parameter.
+
+If DATA is given, it should be a list."
+ (interactive)
+ (emms-lastfm-http-GET
+ (concat emms-lastfm-radio-base-url
+ "np.php?"
+ "session=" emms-lastfm-radio-session
+ "&debug=" (number-to-string 0))
+ (or fn 'emms-lastfm-radio-request-metadata-sentinel)
+ data))
+
+(defun emms-lastfm-radio-request-metadata-sentinel (&rest args)
+ (let ((buffer (current-buffer)))
+ (emms-http-decode-buffer buffer)
+ (let ((artist (emms-key-value "artist" buffer))
+ (title (emms-key-value "track" buffer))
+ (track (emms-playlist-current-selected-track)))
+ (kill-buffer buffer)
+ (emms-track-set track 'info-artist artist)
+ (emms-track-set track 'info-title title)
+ (emms-track-updated track))))
+
+
+;;; Utility functions
+
+(defun emms-read-line ()
+ (buffer-substring-no-properties (line-beginning-position)
+ (line-end-position)))
+
+(defun emms-key-value (key &optional buffer)
+ "Returns the value of KEY from BUFFER.
+If BUFFER is nil, use the current buffer.
+
+BUFFER has to contain a key-value list like:
+
+foo=bar
+x=17"
+ (unless (and buffer (not (buffer-live-p buffer)))
+ (with-current-buffer (or buffer (current-buffer))
+ (goto-char (point-min))
+ (when (re-search-forward (concat "^" key "=") nil t)
+ (buffer-substring-no-properties (point) (line-end-position))))))
+
+(provide 'emms-lastfm)
+;;; emms-lastfm.el ends here