diff options
| -rw-r--r-- | .gitignore | 1 | ||||
| -rw-r--r-- | media/espotify.org | 136 | 
2 files changed, 124 insertions, 13 deletions
| @@ -2,3 +2,4 @@  /media/espotify.el  /media/espotify-consult.el  /media/espotify-embark.el +/media/espotify-counsel.el diff --git a/media/espotify.org b/media/espotify.org index 27329d5..ed3e7f8 100644 --- a/media/espotify.org +++ b/media/espotify.org @@ -4,8 +4,8 @@  #+PROPERTY: header-args :tangle yes :comments no :results silent  (/Note/: you can tangle this file (e.g., with =C-c C-v t= inside Emacs) -into three elisp libraries, =espotify.el=, =espotify-consult.el= and -=espotify-embark=.) +into three elisp libraries, =espotify.el=, =espotify-consult.el, +=espotify-embark=. and =espotify-counsel=)  We have two kinds of interaction with Spotify: via its HTTP API to  perform operations such as search, and via our local DBUS to talk to @@ -215,6 +215,25 @@ Let's start with an umbrella customization group:                            filter)))    #+end_src +* Listing user resources in the Spotify API + +  It is also possible to obtain lists of items of a given type for the +  current user, with a standard URL format: + +  #+begin_src emacs-lisp +    (defun espotify--make-user-url (type) +      (format "%s/me/%ss" espotify-spotify-api-url (symbol-name type))) +  #+end_src + +  and we can then use ~espotify-get~ to offer access to our playlists, +  albums, etc.: + +  #+begin_src emacs-lisp +    (defun espotify-with-user-resources (callback type) +      (espotify-get (lambda (res) (funcall callback (alist-get 'items res))) +                    (espotify--make-user-url type))) +  #+end_src +  * Sending commands to local players    Once we now the URI we want to play (that ~uri~ entry in our items), @@ -252,10 +271,6 @@ Let's start with an umbrella customization group:        (espotify-call-spotify-via-dbus "OpenUri" uri))    #+end_src -  #+begin_src emacs-lisp -    (provide 'espotify) -  #+end_src -  * Search front-end using consult    :PROPERTIES:    :header-args: :tangle espotify-consult.el @@ -338,7 +353,7 @@ Let's start with an umbrella customization group:    play the item (and pass the formatter to ~consult-async--map~, in    ~espotify--search-generator~ above): -  #+begin_src emacs-lisp +  #+begin_src emacs-lisp :tangle espotify.el      (defun espotify--additional-info (x)        (mapconcat 'identity                   (seq-filter 'identity @@ -366,7 +381,7 @@ Let's start with an umbrella customization group:     consult looks up for it using the ~:lookup~ function, which we can     simply define as: -   #+begin_src emacs-lisp :tangle espotify-consult.el +   #+begin_src emacs-lisp       (require 'seq)       (defun espotify--consult-lookup (_input cands cand)         (seq-find (lambda (x) (string= cand x)) cands)) @@ -376,7 +391,7 @@ Let's start with an umbrella customization group:     With that, when we receive the final result from ~consult--read~,     we can play the selected URI right away: -   #+begin_src emacs-lisp +   #+begin_src emacs-lisp :tangle espotify.el       (defun espotify--maybe-play (cand)         (when-let (uri (when cand (espotify--uri cand)))           (espotify-play-uri uri))) @@ -439,10 +454,6 @@ Let's start with an umbrella customization group:                   '(espotify-search-item . espotify-marginalia-annotate))    #+end_src -  #+begin_src emacs-lisp -    (provide 'espotify-consult) -  #+end_src -  * Embark actions    :PROPERTIES:    :header-args: :tangle espotify-embark.el @@ -499,10 +510,109 @@ Let's start with an umbrella customization group:                   '(espotify-search-item . espotify-item-keymap))    #+end_src +* Search fronted using ivy +  :PROPERTIES: +  :header-args: :tangle espotify-counsel.el +  :END: +    #+begin_src emacs-lisp +    ;;; counsel-espotify.el - counsel and spotify -  -*- lexical-binding: t; -*- +    (require 'espotify) +    (require 'ivy) +  #+end_src + +  It is is also not too complicated to provide a counsel collection of +  functions.  Here, we use =ivy-read= to access the completion +  interface, with the flag =dynamic-collection= set.  Ivy will wait +  until we call =ivy-candidate-updates= with our items. + +  #+begin_src emacs-lisp :tangle no +    (defun counsel-espotify--search-by (type filter) +      (lambda (term) +        (espotify-search-all (lambda (its) +                               (let ((cs (mapcar #'espotify--format-item its))) +                                 (ivy-update-candidates cs))) +                             term +                             type +                             filter) +        0)) +  #+end_src + +  With that, we can define our generic completing read: + +  #+begin_src emacs-lisp + +    (defun espotify-counsel--play-album (candidate) +      "Play album associated with selected item." +      (interactive "s") +      (let ((item (espotify--item candidate))) +        (if-let (album (if (string= "album" (alist-get 'type item "")) +                           item +                         (alist-get 'album item))) +            (espotify-play-uri (alist-get 'uri album)) +          (error "No album for %s" (alist-get 'name item))))) + +    (defun espotify--by (type filter) +      (ivy-read (format "Search %s: " type) +                (counsel-espotify--search-by type filter) +                :dynamic-collection t +                :action `(1 ("a" espotify-counsel--play-album "Play album") +                            ("p" espotify--maybe-play ,(format "Play %s" type))))) +  #+end_src + +  #+begin_src emacs-lisp +    (defun counsel-spotify-album (&optional filter) +      (interactive) +      (espotify--by 'album filter)) + +    (defun counsel-spotify-artist (&optional filter) +      (interactive) +      (espotify--by 'artist filter)) + +    (defun counsel-spotify-track (&optional filter) +      (interactive) +      (espotify--by 'track filter)) + +    (defun counsel-spotify-playlist (&optional filter) +      (interactive) +      (espotify--by 'playlist filter)) +  #+end_src + +  Much simpler than consult, although it's true that we already had +  part of the job done.  And we're missing some of the funcionality, +  like on-demand throttling.  That part is not difficult to fix: + +  #+begin_src emacs-lisp +    (defun counsel-espotify--search-by (type filter) +      (lambda (term) +        (when (string-suffix-p "=" (or term "")) +          (espotify-search-all (lambda (its) +                                 (let ((cs (mapcar #'espotify--format-item its))) +                                   (ivy-update-candidates cs))) +                               term +                               type +                               filter)) +        0)) +  #+end_src + +* Postamble + +  #+begin_src emacs-lisp +    (provide 'espotify) +  #+end_src + +  #+begin_src emacs-lisp :tangle espotify-consult.el +    (provide 'espotify-consult) +  #+end_src + +  #+begin_src emacs-lisp :tangle espotify-embark.el      (provide 'espotify-embark)    #+end_src +  #+begin_src emacs-lisp :tangle espotify-counsel.el +    (provide 'espotify-counsel) +  #+end_src +  * Footnotes  [fn:1] This is an elegant strategy i first learnt about in SICP, many, | 
