From b24e28d4726f76c7c69eb65b2aee8f2302c79dfd Mon Sep 17 00:00:00 2001 From: jao Date: Mon, 25 Jan 2021 16:57:15 +0000 Subject: espotify/ivy --- .gitignore | 1 + media/espotify.org | 136 ++++++++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 124 insertions(+), 13 deletions(-) diff --git a/.gitignore b/.gitignore index e18ee79..f39c38f 100644 --- a/.gitignore +++ b/.gitignore @@ -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, -- cgit v1.2.3