From ea7d3100b660201ff5e24d3c53cb403f23b09786 Mon Sep 17 00:00:00 2001 From: Yoni Rabkin Date: Thu, 24 Dec 2009 21:30:32 +0200 Subject: Replace lisp/emms-lastfm.el with lisp/emms-lastfm-client.el. The original emms-lastfm.el doesn't work at all with the current Last.fm API. --- lisp/emms-lastfm-client.el | 743 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 743 insertions(+) create mode 100644 lisp/emms-lastfm-client.el (limited to 'lisp/emms-lastfm-client.el') diff --git a/lisp/emms-lastfm-client.el b/lisp/emms-lastfm-client.el new file mode 100644 index 0000000..20ec018 --- /dev/null +++ b/lisp/emms-lastfm-client.el @@ -0,0 +1,743 @@ +;;; -*- show-trailing-whitespace: t -*- +;;; emms-lastfm-client.el --- Last.FM Music API + +;; Copyright (C) 2009 Free Software Foundation, Inc. + +;; Author: Yoni Rabkin + +;; 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. + +;;; Commentary: +;; +;; There are restrictions on the use of this service. Quoting from +;; [http://www.last.fm/api/radio]: "Who can I stream radio to? Any API +;; account can only stream radio to Last.fm's paid subscribers". +;; +;; We've spoken to representatives from Last.fm and arrived at the +;; following agreement: In order to be able to use the service while +;; preserving the essential freedoms of the GPL each client must apply +;; for their own API key from Last.fm. + +;;; Installation: +;; +;; Here is how to get authorization from Last.fm to stream +;; music. Thankfully this only needs to be done _once_: +;; +;; 1. Complete steps 1 and 2 from +;; [http://www.last.fm/api/authentication] to get an API key and a +;; secret key. Set `emms-lastfm-client-api-key' and +;; `emms-lastfm-client-api-secret-key' accordingly. +;; +;; 2. M-x emms-lastfm-client-user-authorization +;; +;; If this completes successfully a browser window will open asking +;; for confirmation to allow this application to access the Last.fm +;; account. Confirm and close the browser. +;; +;; 3. M-x emms-lastfm-client-get-session +;; +;; After this last step the permanent session key will be stored in +;; `emms-lastfm-client-session-key-file'. As long as this key value +;; is accessible the authentication process need never be repeated. + +;;; Use: +;; +;; M-x emms-lastfm-client-play-similar-artists +;; +;; Call the ...-play- family of interactive functions, such the above, +;; to start streaming music. +;; +;; To show the currently playing track: M-x emms-lastfm-client-show +;; +;; To skip to the next track: M-x emms-lastfm-client-track-advance +;; +;; ...more Last.fm functionality to come, so stay tuned. + +;;; Code: + +(require 'md5) +(require 'xml) + +(defvar emms-lastfm-client-api-key nil + "Key for the Last.fm API.") + +(defvar emms-lastfm-client-api-secret-key nil + "Secret key for the Last.fm API.") + +(defvar emms-lastfm-client-api-session-key nil + "Session key for the Last.fm API.") + +(defvar emms-lastfm-client-token nil + "Authorization token for API.") + +(defvar emms-lastfm-client-api-base-url + "http://ws.audioscrobbler.com/2.0/" + "URL for API calls.") + +(defvar emms-lastfm-client-session-key-file + (concat (file-name-as-directory emms-directory) + "emms-lastfm-client-sessionkey") + "File for storing the Last.fm API session key.") + +(defvar emms-lastfm-client-playlist-valid nil + "True if the playlist hasn't expired.") + +(defvar emms-lastfm-client-playlist-timer nil + "Playlist timer object.") + +(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' to be restored.") + +(defvar emms-lastfm-client-playlist-buffer-name "*Emms Last.fm*" + "Name for non-interactive Emms Last.fm buffer.") + +(defvar emms-lastfm-client-playlist-buffer nil + "Non-interactive Emms Last.fm buffer.") + +(defvar emms-lastfm-client-api-method-dict + '((auth-get-token . ("auth.gettoken" + emms-lastfm-client-auth-get-token-ok + emms-lastfm-client-auth-get-token-failed)) + (auth-get-session . ("auth.getsession" + emms-lastfm-client-auth-get-session-ok + emms-lastfm-client-auth-get-session-failed)) + (radio-tune . ("radio.tune" + emms-lastfm-client-radio-tune-ok + emms-lastfm-client-radio-tune-failed)) + (radio-getplaylist . ("radio.getplaylist" + emms-lastfm-client-radio-getplaylist-ok + emms-lastfm-client-radio-getplaylist-failed))) + "Mapping symbols to method calls. This is a list of cons pairs + where the CAR is the symbol name of the method and the CDR is a + list whose CAR is the method call string, CADR is the function + to call on a success and CADDR is the function to call on + failure.") + +;;; ------------------------------------------------------------------ +;;; API method call +;;; ------------------------------------------------------------------ + +(defun emms-lastfm-client-get-method (method) + "Return the associated method cons for the symbol METHOD." + (let ((m (cdr (assoc method emms-lastfm-client-api-method-dict)))) + (if (not m) + (error "method not in dictionary: %s" method) + m))) + +(defun emms-lastfm-client-get-method-name (method) + "Return the associated method string for the symbol METHOD." + (let ((this (nth 0 (emms-lastfm-client-get-method method)))) + (if (not this) + (error "no name string registered for method: %s" method) + this))) + +(defun emms-lastfm-client-get-method-ok (method) + "Return the associated OK function for METHOD. + +This function is called when the method call returns +successfully." + (let ((this (nth 1 (emms-lastfm-client-get-method method)))) + (if (not this) + (error "no OK function registered for method: %s" method) + this))) + +(defun emms-lastfm-client-get-method-fail (method) + "Return the associated fail function for METHOD. + +This function is called when the method call returns a failure +status message." + (let ((this (nth 2 (emms-lastfm-client-get-method method)))) + (if (not this) + (error "no fail function registered for method: %s" method) + this))) + +(defun emms-lastfm-client-encode-arguments (arguments) + "Encode ARGUMENTS in UTF-8 for the Last.fm API." + (let ((result nil)) + (while arguments + (setq result + (append result + (list + (cons + (encode-coding-string (caar arguments) 'utf-8) + (encode-coding-string (cdar arguments) 'utf-8))))) + (setq arguments (cdr arguments))) + result)) + +(defun emms-lastfm-client-construct-arguments (str arguments) + "Return a concatenation of arguments for the URL." + (cond ((not arguments) str) + (t (emms-lastfm-client-construct-arguments + (concat str "&" (caar arguments) "=" (url-hexify-string (cdar arguments))) + (cdr arguments))))) + +(defun emms-lastfm-client-construct-method-call (method arguments) + "Return a complete URL method call for METHOD with ARGUMENTS. + +This function includes the cryptographic signature." + (concat emms-lastfm-client-api-base-url "?" + "method=" (emms-lastfm-client-get-method-name method) + (emms-lastfm-client-construct-arguments + "" arguments) + "&api_sig=" + (emms-lastfm-client-construct-signature method arguments))) + +(defun emms-lastfm-client-construct-write-method-call (method arguments) + "Return a complete POST body method call for METHOD with ARGUMENTS. + +This function includes the cryptographic signature." + (concat "method=" (emms-lastfm-client-get-method-name method) + (emms-lastfm-client-construct-arguments + "" arguments) + "&api_sig=" + (emms-lastfm-client-construct-signature method arguments))) + +;;; ------------------------------------------------------------------ +;;; Response handler +;;; ------------------------------------------------------------------ + +(defun emms-lastfm-client-handle-response (method xml-response) + "Dispatch the handler functions of METHOD for XML-RESPONSE." + (let ((status (cdr (assoc 'status (nth 1 (car xml-response))))) + (data (cddar xml-response))) + (when (not status) + (error "error parsing status from: %s" xml-response)) + (cond ((string= status "failed") + (funcall (emms-lastfm-client-get-method-fail method) data)) + ((string= status "ok") + (funcall (emms-lastfm-client-get-method-ok method) data)) + (t (error "unknown response status %s" status))))) + +;;; ------------------------------------------------------------------ +;;; Unathorized request token for an API account +;;; ------------------------------------------------------------------ + +(defun emms-lastfm-client-construct-urt () + "Return a request for an Unauthorized Request Token." + (let ((arguments + (emms-lastfm-client-encode-arguments + `(("api_key" . ,emms-lastfm-client-api-key))))) + (emms-lastfm-client-construct-method-call + 'auth-get-token arguments))) + +(defun emms-lastfm-client-make-call-urt () + "Make method call for Unauthorized Request Token." + (let* ((url-request-method "POST")) + (let ((response + (url-retrieve-synchronously + (emms-lastfm-client-construct-urt)))) + (emms-lastfm-client-handle-response + 'auth-get-token + (with-current-buffer response + (xml-parse-region (point-min) (point-max))))))) + +;; example response: ((lfm ((status . \"ok\")) \"\" (token nil +;; \"31cab3398a9b46cf7231ef84d73169cf\"))) + +;;; ------------------------------------------------------------------ +;;; Signatures +;;; ------------------------------------------------------------------ +;; +;; From [http://www.last.fm/api/desktopauth]: +;; +;; Construct your api method signatures by first ordering all the +;; parameters sent in your call alphabetically by parameter name and +;; concatenating them into one string using a +;; scheme. So for a call to auth.getSession you may have: +;; +;; api_keyxxxxxxxxmethodauth.getSessiontokenxxxxxxx +;; +;; Ensure your parameters are utf8 encoded. Now append your secret +;; to this string. Finally, generate an md5 hash of the resulting +;; string. For example, for an account with a secret equal to +;; 'mysecret', your api signature will be: +;; +;; api signature = md5("api_keyxxxxxxxxmethodauth.getSessiontokenxxxxxxxmysecret") +;; +;; Where md5() is an md5 hashing operation and its argument is the +;; string to be hashed. The hashing operation should return a +;; 32-character hexadecimal md5 hash. + +(defun emms-lastfm-client-construct-lexi (arguments) + "Return ARGUMENTS sorted in lexicographic order." + (let ((lexi (sort arguments + '(lambda (a b) (string< (car a) (car b))))) + (out "")) + (while lexi + (setq out (concat out (caar lexi) (cdar lexi))) + (setq lexi (cdr lexi))) + out)) + +(defun emms-lastfm-client-construct-signature (method arguments) + "Return request signature for METHOD and ARGUMENTS." + (let ((complete-arguments + (append arguments + `(("method" . + ,(emms-lastfm-client-get-method-name method)))))) + (md5 + (concat (emms-lastfm-client-construct-lexi complete-arguments) + emms-lastfm-client-api-secret-key)))) + +;;; ------------------------------------------------------------------ +;;; General error handling +;;; ------------------------------------------------------------------ + +;; Each method call provides its own error codes, but if we don't want +;; to code a handler for a method we call this instead: +(defun emms-lastfm-client-default-error-handler (data) + "Default method failure handler." + (let ((errorcode (cdr (assoc 'code (nth 1 (cadr data))))) + (message (nth 2 (cadr data)))) + (when (not (and errorcode message)) + (error "failed to read errorcode or message: %s %s" + errorcode message)) + (error "method call failed with code %s: %s" + errorcode message))) + +;;; ------------------------------------------------------------------ +;;; Request authorization from the user +;;; ------------------------------------------------------------------ + +(defun emms-lastfm-client-ask-for-auth () + "Open a Web browser for authorizing the application." + (when (not (and emms-lastfm-client-api-key + emms-lastfm-client-token)) + (error "API key and authorization token needed.")) + (browse-url + (format "http://www.last.fm/api/auth/?api_key=%s&token=%s" + emms-lastfm-client-api-key + emms-lastfm-client-token))) + +;;; ------------------------------------------------------------------ +;;; Parse XSPF +;;; ------------------------------------------------------------------ + +(defun emms-lastfm-client-xspf-header (data) + "Return an alist representing the XSPF header of DATA." + (let (out + (orig data)) + (setq data (cadr data)) + (while data + (when (and (car data) + (listp (car data)) + (= (length (car data)) 3)) + (setq out (append out (list (cons (nth 0 (car data)) + (nth 2 (car data))))))) + (setq data (cdr data))) + (if (not out) + (error "failed to parse XSPF header from: %s" orig) + out))) + +(defun emms-lastfm-client-xspf-tracklist (data) + "Return the start of the track-list in DATE." + (nthcdr 3 (nth 11 (cadr data)))) + +(defun emms-lastfm-client-xspf-header-date (header-alist) + "Return the date parameter from HEADER-ALIST." + (let ((out (cdr (assoc 'date header-alist)))) + (if (not out) + (error "could not read date from header alist: %s" + header-alist) + out))) + +(defun emms-lastfm-client-xspf-header-expiry (header-alist) + "Return the expiry parameter from HEADER-ALIST." + (let ((out (cdr (assoc 'link header-alist)))) + (if (not out) + (error "could not read expiry from header alist: %s" + header-alist) + out))) + +(defun emms-lastfm-client-xspf-header-creator (header-alist) + "Return the creator parameter from HEADER-ALIST." + (let ((out (cdr (assoc 'creator header-alist)))) + (if (not out) + (error "could not read creator from header alist: %s" + header-alist) + out))) + +(defun emms-lastfm-client-xspf-playlist (data) + "Return the playlist from the XSPF DATA." + (let ((playlist (car (nthcdr 11 data)))) + (if (not playlist) + (error "could not read playlist from: %s" data) + playlist))) + +(defun emms-lastfm-client-xspf-get (node track) + "Return data associated with NODE in TRACK." + (let ((result nil)) + (while track + (when (consp track) + (let ((this (car track))) + (when (and (consp this) + (= (length this) 3) + (symbolp (nth 0 this)) + (stringp (nth 2 this)) + (equal (nth 0 this) node)) + (setq result (nth 2 this))))) + (setq track (cdr track))) + (if (not result) + nil + result))) + +;;; ------------------------------------------------------------------ +;;; Timers +;;; ------------------------------------------------------------------ + +;; timed playlist invalidation is a part of the Last.fm API +(defun emms-lastfm-client-set-timer (header) + "Start timer countdown to playlist invalidation" + (when (not header) + (error "can't set timer with no header data")) + (let ((expiry (parse-integer + (emms-lastfm-client-xspf-header-expiry header)))) + (setq emms-lastfm-client-playlist-valid t) + (setq emms-lastfm-client-playlist-timer + (run-at-time + expiry nil + '(lambda () (setq emms-lastfm-client-playlist-valid + nil)))))) + +;;; ------------------------------------------------------------------ +;;; Player +;;; ------------------------------------------------------------------ + +;; this should return `nil' to the track-manager when the playlist has +;; been exhausted +(defun emms-lastfm-client-consume-next-track () + "Pop and return the next track from the playlist or nil." + (when emms-lastfm-client-playlist + (if emms-lastfm-client-playlist-valid + (let ((track (car emms-lastfm-client-playlist))) + ;; we can only request each track once so we pop it off the + ;; playlist + (setq emms-lastfm-client-playlist + (if (stringp (cdr emms-lastfm-client-playlist)) + (cddr emms-lastfm-client-playlist) + (cdr emms-lastfm-client-playlist))) + track) + (error "playlist invalid")))) + +(defun emms-lastfm-client-set-lastfm-playlist-buffer () + (when (not (buffer-live-p emms-lastfm-client-playlist-buffer)) + (setq emms-lastfm-client-playlist-buffer + (emms-playlist-new + emms-lastfm-client-playlist-buffer-name)) + (setq emms-playlist-buffer emms-lastfm-client-playlist-buffer))) + +(defun emms-lastfm-client-load-next-track () + (with-current-buffer emms-lastfm-client-playlist-buffer + (emms-playlist-clear) + (if emms-lastfm-client-playlist + (let ((track (emms-lastfm-client-consume-next-track))) + (setq emms-lastfm-client-track track) + (when emms-player-playing-p + (emms-stop)) + (emms-play-url + (emms-lastfm-client-xspf-get 'location track))) + (emms-lastfm-client-make-call-radio-getplaylist) + (emms-lastfm-client-load-next-track)))) + +;; call this `-track-advance' to avoid confusion with Emms' +;; `-next-track-' mechanism +(defun emms-lastfm-client-track-advance () + (interactive) + (when (equal emms-playlist-buffer + emms-lastfm-client-playlist-buffer) + (emms-lastfm-client-load-next-track))) + +(defun emms-lastfm-client-play-playlist () + "Entry point to play tracks from Last.fm." + (emms-lastfm-client-set-lastfm-playlist-buffer) + (add-hook 'emms-player-finished-hook + 'emms-lastfm-client-track-advance) + (emms-lastfm-client-track-advance)) + +;; stolen from Tassilo Horn's original emms-lastfm.el +(defun emms-lastfm-client-read-artist () + "Read an artist name from the user." + (let ((artists nil)) + (when (boundp 'emms-cache-db) + (maphash + #'(lambda (file track) + (let ((artist (emms-track-get track 'info-artist))) + (when artist + (add-to-list 'artists artist)))) + emms-cache-db)) + (if artists + (emms-completing-read "Artist: " artists) + (read-string "Artist: ")))) + +(defun emms-lastfm-client-play-similar-artists (artist) + "Play a Last.fm station with music similar to ARTIST." + (interactive (list (emms-lastfm-client-read-artist))) + (when (not (stringp artist)) + (error "not a string: %s" artist)) + (emms-lastfm-client-check-session-key) + (emms-lastfm-client-make-call-radio-tune + (format "lastfm://artist/%s/similarartists" artist)) + (emms-lastfm-client-make-call-radio-getplaylist) + (emms-lastfm-client-play-playlist)) + +;;; ------------------------------------------------------------------ +;;; Information +;;; ------------------------------------------------------------------ + +(defun emms-lastfm-client-convert-track (track) + "Convert a Last.fm track to an Emms track." + (let ((emms-track (emms-dictionary '*track*))) + (emms-track-set emms-track 'name + (emms-lastfm-client-xspf-get 'location track)) + (emms-track-set emms-track 'info-artist + (emms-lastfm-client-xspf-get 'creator track)) + (emms-track-set emms-track 'info-title + (emms-lastfm-client-xspf-get 'title track)) + (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)) + emms-track)) + +(defun emms-lastfm-client-show-track (track) + "Return description of TRACK." + (decode-coding-string + (format emms-show-format + (emms-track-description + (emms-lastfm-client-convert-track track))) + 'utf-8)) + +(defun emms-lastfm-client-show () + "Display a description of the current track." + (interactive) + (if emms-player-playing-p + (message + (emms-lastfm-client-show-track emms-lastfm-client-track)) + nil)) + +;;; ------------------------------------------------------------------ +;;; Desktop application authorization [http://www.last.fm/api/desktopauth] +;;; ------------------------------------------------------------------ + +(defun emms-lastfm-client-user-authorization () + "Ask user to authorize the application." + (interactive) + (emms-lastfm-client-make-call-urt) + (emms-lastfm-client-ask-for-auth)) + +(defun emms-lastfm-client-get-session () + "Retrieve and store session key." + (interactive) + (emms-lastfm-client-make-call-get-session) + (emms-lastfm-client-save-session-key + emms-lastfm-client-api-session-key)) + +;;; ------------------------------------------------------------------ +;;; method: auth.getToken [http://www.last.fm/api/show?service=265] +;;; ------------------------------------------------------------------ + +(defun emms-lastfm-client-auth-get-token-ok (data) + "Function called when auth.getToken succeeds." + (setq emms-lastfm-client-token + (nth 2 (cadr data))) + (if (or (not emms-lastfm-client-token) + (not (= (length emms-lastfm-client-token) 32))) + (error "could not read token from response %s" data) + (message "Emms Last.FM auth.getToken method call success."))) + +(defun emms-lastfm-client-auth-get-token-failed (data) + "Function called when auth.getToken fails." + (emms-lastfm-client-default-error-handler data)) + +;;; ------------------------------------------------------------------ +;;; method: auth.getSession [http://www.last.fm/api/show?service=125] +;;; ------------------------------------------------------------------ + +(defun emms-lastfm-client-construct-get-session () + "Return an auth.getSession request string." + (let ((arguments + (emms-lastfm-client-encode-arguments + `(("token" . ,emms-lastfm-client-token) + ("api_key" . ,emms-lastfm-client-api-key))))) + (emms-lastfm-client-construct-method-call + 'auth-get-session arguments))) + +(defun emms-lastfm-client-make-call-get-session () + "Make auth.getSession call." + (let* ((url-request-method "POST")) + (let ((response + (url-retrieve-synchronously + (emms-lastfm-client-construct-get-session)))) + (emms-lastfm-client-handle-response + 'auth-get-session + (with-current-buffer response + (xml-parse-region (point-min) (point-max))))))) + +(defun emms-lastfm-client-save-session-key (key) + "Store KEY." + (let ((buffer (find-file-noselect + emms-lastfm-client-session-key-file))) + (set-buffer buffer) + (erase-buffer) + (insert key) + (save-buffer) + (kill-buffer buffer))) + +(defun emms-lastfm-client-load-session-key () + "Return stored session key." + (let ((file (expand-file-name emms-lastfm-client-session-key-file))) + (setq emms-lastfm-client-api-session-key + (if (file-readable-p file) + (with-temp-buffer + (emms-insert-file-contents file) + (goto-char (point-min)) + (buffer-substring-no-properties + (point) (point-at-eol))) + nil)))) + +(defun emms-lastfm-client-check-session-key () + "Signal an error condition if there is no session key." + (if emms-lastfm-client-api-session-key + emms-lastfm-client-api-session-key + (if (emms-lastfm-client-load-session-key) + emms-lastfm-client-api-session-key + (error "no session key for API access")))) + +(defun emms-lastfm-client-auth-get-session-ok (data) + "Function called on DATA if auth.getSession succeeds." + (let ((session-key (nth 2 (nth 5 (cadr data))))) + (cond (session-key + (setq emms-lastfm-client-api-session-key + session-key) + (message "Emms Last.fm session key retrieval successful" + session-key)) + (t (error "failed to parse session key data %s" data))))) + +(defun emms-lastfm-client-auth-get-session-failed (data) + "Function called on DATA if auth.getSession fails." + (emms-lastfm-client-default-error-handler data)) + +;;; ------------------------------------------------------------------ +;;; method: radio.tune [http://www.last.fm/api/show?service=160] +;;; ------------------------------------------------------------------ + +(defun emms-lastfm-client-construct-radio-tune (station) + "Return a request to tune to STATION." + (let ((arguments + (emms-lastfm-client-encode-arguments + `(("sk" . ,emms-lastfm-client-api-session-key) + ("station" . ,station) + ("api_key" . ,emms-lastfm-client-api-key))))) + (emms-lastfm-client-construct-write-method-call + 'radio-tune arguments))) + +(defun emms-lastfm-client-make-call-radio-tune (station) + "Make call to tune to STATION." + (let ((url-request-method "POST") + (url-request-extra-headers + `(("Content-type" . "application/x-www-form-urlencoded"))) + (url-request-data + (emms-lastfm-client-construct-radio-tune station))) + (let ((response + (url-retrieve-synchronously + emms-lastfm-client-api-base-url))) + (emms-lastfm-client-handle-response + 'radio-tune + (with-current-buffer response + (xml-parse-region (point-min) (point-max))))))) + +(defun emms-lastfm-client-radio-tune-failed (data) + "Function called on DATA when tuning fails." + (emms-lastfm-client-default-error-handler data)) + +(defun emms-lastfm-client-radio-tune-ok (data) + "Set the current radio station according to DATA." + (let ((response (cdr (cadr data))) + data) + (while response + (when (and (listp (car response)) + (car response) + (= (length (car response)) 3)) + (add-to-list 'data (cons (caar response) + (caddr (car response))))) + (setq response (cdr response))) + (when (not data) + (error "could not parse station information %s" data)) + (setq emms-lastfm-client-tuned-station-alist data))) + +;;; ------------------------------------------------------------------ +;;; method: radio.getPlaylist [http://www.last.fm/api/show?service=256] +;;; ------------------------------------------------------------------ + +(defun emms-lastfm-client-construct-radio-getplaylist () + "Return a request for a playlist from the tuned station." + (let ((arguments + (emms-lastfm-client-encode-arguments + `(("sk" . ,emms-lastfm-client-api-session-key) + ("api_key" . ,emms-lastfm-client-api-key))))) + (emms-lastfm-client-construct-write-method-call + 'radio-getplaylist arguments))) + +(defun emms-lastfm-client-make-call-radio-getplaylist () + "Make call for playlist from the tuned station." + (let ((url-request-method "POST") + (url-request-extra-headers + `(("Content-type" . "application/x-www-form-urlencoded"))) + (url-request-data + (emms-lastfm-client-construct-radio-getplaylist))) + (let ((response + (url-retrieve-synchronously + emms-lastfm-client-api-base-url))) + (emms-lastfm-client-handle-response + 'radio-getplaylist + (with-current-buffer response + (xml-parse-region (point-min) (point-max))))))) + +(defun emms-lastfm-client-radio-getplaylist-failed (data) + "Function called on DATA when retrieving a playlist fails." + 'stub-needs-to-handle-playlist-issues + (emms-lastfm-client-default-error-handler data)) + +(defun emms-lastfm-client-list-filter (l) + "Remove strings from the roots of list L." + (let (acc) + (while l + (when (listp (car l)) + (push (car l) acc)) + (setq l (cdr l))) + (reverse acc))) + +(defun emms-lastfm-client-radio-getplaylist-ok (data) + "Function called on DATA when retrieving a playlist succeeds." + (let ((header (emms-lastfm-client-xspf-header data)) + (tracklist (emms-lastfm-client-xspf-tracklist data))) + (emms-lastfm-client-set-timer header) + (setq emms-lastfm-client-playlist + (emms-lastfm-client-list-filter tracklist)))) + +(provide 'emms-lastfm-client) + +;;; emms-lastfm-client.el ends here -- cgit v1.2.3