Where's the instinct?

This commit is contained in:
nik gaffney 2023-05-22 16:49:11 +02:00
parent 6a27268134
commit 17f39bdf1f
3 changed files with 168 additions and 67 deletions

View file

@ -164,6 +164,7 @@ Search supports the full [[https://lucene.apache.org/core/7_7_2/queryparser/org
| (musicbrainz-search-label /label/ &optional /limit/) | | | (musicbrainz-search-label /label/ &optional /limit/) | |
| (musicbrainz-search-recording /query/ &optional /limit/) | | | (musicbrainz-search-recording /query/ &optional /limit/) | |
| (musicbrainz-search-release /query/ &optional /limit/) | | | (musicbrainz-search-release /query/ &optional /limit/) | |
| (musicbrainz-search-work /query/ &optional /limit/) | |
*** Lookup queries & subqueries *** Lookup queries & subqueries
@ -199,7 +200,7 @@ Search supports the full [[https://lucene.apache.org/core/7_7_2/queryparser/org
| (musicbrainz-lookup-release-group-artists /MBID/) | … | | (musicbrainz-lookup-release-group-artists /MBID/) | … |
| (musicbrainz-lookup-release-group-releases /MBID/) | … | | (musicbrainz-lookup-release-group-releases /MBID/) | … |
| (musicbrainz-lookup-series /MBID/) | … | | (musicbrainz-lookup-series /MBID/) | … |
| (musicbrainz-lookup-work /MBID/) | | | (musicbrainz-lookup-work /MBID/) | [[https://musicbrainz.org/work/4ee2545d-2be5-3841-b568-0b4554eccc67][4ee2545d-2be5-3841-b568-0b4554eccc67]] |
| (musicbrainz-lookup-url /MBID/) | … | | (musicbrainz-lookup-url /MBID/) | … |

View file

@ -27,10 +27,19 @@
;;; Commentary: ;;; Commentary:
;; - listen & submit metadata to ListenBrainz ;; An interface to ListenBrainz, a project to store a record of the music that
;; - partial & incomplete ;; you listen to. The listening data, can be used to provide statistics,
;; - no error checks ;; recommendations and general exploration.
;; - sync -> async ;;
;; The package can be used programmatically (e.g. from a music player) to auto
;; submit listening data `listenbrainz-submit-listen'. There are other entrypoints
;; for reading user stats such as `listenbrainz-stats-artists' or
;; `listenbrainz-listens'.
;;
;; Some API calls require a user token, which can be found in your ListenBrainz
;; profile. Configure, set or `customize' the `listenbrainz-api-token' as needed.
;;
;; https://listenbrainz.readthedocs.io/
;;; Code: ;;; Code:
@ -104,7 +113,6 @@ All timestamps used in ListenBrainz are UNIX epoch timestamps in UTC."
;; ;;
;;;; ; ;; ; ;;;; ; ;; ;
(defmacro listenbrainz--deformatter (name format-string format-args alist) (defmacro listenbrainz--deformatter (name format-string format-args alist)
"Generate function with NAME to format data returned from an API call. "Generate function with NAME to format data returned from an API call.
The function has the name `listenbrainz--format-NAME`. The function has the name `listenbrainz--format-NAME`.
@ -216,7 +224,6 @@ macroexpands to something like ->
;; ;;
;;; ; ;; ; ; ; ;;; ; ;; ; ; ;
;;;###autoload
(defun listenbrainz-validate-token (token) (defun listenbrainz-validate-token (token)
"Check if TOKEN is valid. Return a username or nil." "Check if TOKEN is valid. Return a username or nil."
(message "listenbrainz: checking token %s" token) (message "listenbrainz: checking token %s" token)

View file

@ -27,22 +27,23 @@
;;; Commentary: ;;; Commentary:
;; - basic MusicBrainz interface ;; An interface to the MusicBrainz "open music encyclopedia" collection
;; - partial & incomplete ;; of music metadata. The main entry points are `musicbrainz-search' for
;; - no error checks ;; general searches and `musicbrainz-lookup' for the more specific.
;; - sync -> async ;; There are also some narrower searches such as `musicbrainz-search-artist'
;;
;; Naming follows the MusicBrainz API reasonably closely, so the official API
;; documentation can provide insight into how searching, browsing and lookups
;; are structured. MusicBrainz has it's particular taxonomy and quirks, so
;; some familiarity may be required to get useful results in some cases.
;;
;; https://musicbrainz.org/doc/MusicBrainz_API
;;; Code: ;;; Code:
(require 'request) (require 'request)
(require 'json) (require 'json)
;; debug level for http requests
(setq request-log-level 'warn
request-message-level 'warn)
;;; ;; ;; ; ; ; ; ; ; ;;; ;; ;; ; ; ; ; ; ;
;; ;;
;; API config ;; API config
@ -113,6 +114,12 @@ As seen in https://wiki.musicbrainz.org/MusicBrainz_API/Rate_Limiting"
"series" "tag" "work" "url") "series" "tag" "work" "url")
"Valid TYPE parameters for MusicBrainz searches.") "Valid TYPE parameters for MusicBrainz searches.")
(defconst musicbrainz-relationships
(list "area-rels" "artist-rels" "event-rels" "instrument-rels"
"label-rels" "place-rels" "recording-rels" "release-rels"
"release-group-rels" "series-rels" "url-rels" "work-rels")
"Valid relationships for lookups.")
;; entity checks ;; entity checks
@ -175,6 +182,10 @@ An MBID is a 36 character Universally Unique Identifier, see https://musicbrainz
mbid)) mbid))
t nil)) t nil))
;; https://lucene.apache.org/core/4_3_0/queryparser/org/apache/lucene/queryparser/classic/package-summary.html#Escaping_Special_Characters
(defconst musicbrainz-qeury-special-chars
(list "+" "-" "&" "|" "!" "(" ")" "{" "}" "[" "]" "^" "\"" "~" "*" "?" ":" "\\" "/"))
(defun musicbrainz-format (response) (defun musicbrainz-format (response)
"Format a generic RESPONSE." "Format a generic RESPONSE."
@ -228,6 +239,7 @@ can be found near https://musicbrainz.org/doc/MusicBrainz_API/Search
or in the Lucene docs." or in the Lucene docs."
(message "MusicBrainz: searching %s=%s" type query) (message "MusicBrainz: searching %s=%s" type query)
;; queries may need to be escaped
(let* ((max (if limit limit 1)) (let* ((max (if limit limit 1))
(from (if offset offset "")) (from (if offset offset ""))
(response (response
@ -264,12 +276,59 @@ Heuristics.
(message "searching mbid: %s" mbid)) (message "searching mbid: %s" mbid))
;; search (search/browse/query) for other things ;; search (search/browse/query) for other things
(progn (progn
(message "searching other: %s" mbid) (message "searching other: %s" mbid))))
)))
;; generate search functions
(defmacro musicbrainz--defsearch-1 (name format-string format-args)
"Generate search function to format a single item.
NAME FORMAT-STRING FORMAT-ARGS
See listenbrainz--deformatter for details."
(let ((f (intern (concat "musicbrainz-search-" name)))
(doc (format "Search for %s using QUERY and show matches.
Optionally return LIMIT number of results." name)))
`(defun ,f (query &optional limit) ,doc
(let* ((max (if limit limit 1))
(response
(musicbrainz-search ,name query max)))
(let-alist response
(format ,format-string ,@format-args))))))
(defmacro musicbrainz--defsearch-2 (name format-string format-args alist)
"Generate lookup function to format multiple items.
QUERY SUBQUERY FORMAT-STRING FORMAT-ARGS ALIST
See listenbrainz--deformatter for details."
(let ((f (intern (concat "musicbrainz-search-" name)))
(doc (format "Search for %s using QUERY and show matches.
Optionally return LIMIT number of results." name)))
`(defun ,f (query &optional limit) ,doc
(let* ((max (if limit limit 1))
(response
(musicbrainz-search ,name query max)))
(let-alist response
(seq-map
(lambda (i)
(let-alist i
(format ,format-string ,@format-args)))
,alist))))))
;; various specific searches ;; various specific searches
;; search -> musicbrainz-search-annotation
(musicbrainz--defsearch-2 "annotation"
"%s | %s | %s | %s | [[https://musicbrainz.org/%s/%s][%s]] |\n"
(.score .type .name .text .type .entity .entity)
.annotations)
;; search -> musicbrainz-search-area
(musicbrainz--defsearch-2 "area"
"%s | [[https://musicbrainz.org/area/%s][%s]] |\n"
(.name .id .id)
.areas)
(defun musicbrainz-search-artist (artist &optional limit) (defun musicbrainz-search-artist (artist &optional limit)
"Search for an ARTIST and show matches. "Search for an ARTIST and show matches.
Optionally return LIMIT number of results." Optionally return LIMIT number of results."
@ -314,6 +373,19 @@ Outputs an `org-mode' table with descriptions and MBID link to artists pages."
.artists))))) .artists)))))
;; search -> musicbrainz-search-event
(musicbrainz--defsearch-2 "event"
"%s | [[https://musicbrainz.org/event/%s][%s]] |\n"
(.name .id .id)
.events)
;; search -> musicbrainz-search-instrument
(musicbrainz--defsearch-2 "instrument"
"| %s | %s | [[https://musicbrainz.org/instrument/%s][%s]] |\n"
(.name .type .id .id)
.instruments)
(defun musicbrainz-search-label (label &optional limit) (defun musicbrainz-search-label (label &optional limit)
"Search for a LABEL and show matches. "Search for a LABEL and show matches.
Optionally return LIMIT number of results." Optionally return LIMIT number of results."
@ -337,32 +409,47 @@ Optionally return LIMIT number of results."
.labels)))) .labels))))
(defun musicbrainz-search-recording (query &optional limit) ;; search -> musicbrainz-search-place
"Search for a recording using QUERY and show matches. (musicbrainz--defsearch-2 "place"
Optionally return LIMIT number of results." "%s | [[https://musicbrainz.org/place/%s][%s]] |\n"
(let ((data (musicbrainz-search "recording" query limit))) (.name .id .id)
(let-alist .places)
data
(seq-map
(lambda (i)
(let-alist i
(format "%s | %s, %s | [[https://musicbrainz.org/release/%s][%s]] |\n"
.score .title (musicbrainz--unwrap-0 .artist-credit) .id .id)))
.recordings))))
;; search -> musicbrainz-search-recording
(musicbrainz--defsearch-2 "recording"
"%s | %s, %s | [[https://musicbrainz.org/recording/%s][%s]] |\n"
(.score .title (musicbrainz--unwrap-0 .artist-credit) .id .id)
.recordings)
(defun musicbrainz-search-release (query &optional limit) ;; search -> musicbrainz-search-release
"Search for a release using QUERY and show matches. (musicbrainz--defsearch-2 "release"
Optionally return LIMIT number of results." "%s | %s | %s | [[https://musicbrainz.org/release/%s][%s]] |\n"
(let ((data (musicbrainz-search "release" query limit))) (.score .title (musicbrainz--unwrap-0 .artist-credit) .id .id)
(let-alist .releases)
data
(seq-map ;; search -> musicbrainz-search-release-group
(lambda (i) (musicbrainz--defsearch-2 "release-group"
(let-alist i "%s | %s | %s | [[https://musicbrainz.org/release-group/%s][%s]] |\n"
(format "%s | %s, %s | [[https://musicbrainz.org/release/%s][%s]] |\n" (.first-release-date .title .primary-type .id .id)
.score .title (musicbrainz--unwrap-0 .artist-credit) .id .id))) .release-groups)
.releases))))
;; search -> musicbrainz-search-series
(musicbrainz--defsearch-2 "series"
"%s | [[https://musicbrainz.org/series/%s][%s]] |\n"
(.name .id .id)
.series)
;; search -> musicbrainz-search-work
(musicbrainz--defsearch-2 "work"
"%s | %s | [[https://musicbrainz.org/work/%s][%s]] |\n"
(.score .title .id .id)
.works)
;; search -> musicbrainz-search-url
(musicbrainz--defsearch-2 "url"
"%s | [[%s][%s]] | [[https://musicbrainz.org/url/%s][%s]] |\n"
(.score .resource .resource .id .id)
.urls)
;;; ;; ;; ; ; ; ; ; ; ;;; ;; ;; ; ; ; ; ; ;
@ -413,6 +500,13 @@ Subqueries
(error "MusicBrainz: search requires a valid MBID and entity (i.e. one of %s)" (error "MusicBrainz: search requires a valid MBID and entity (i.e. one of %s)"
musicbrainz-entities-core))) musicbrainz-entities-core)))
;; relationship lookups
(defun musicbrainz-relations (entity relation mbid)
"Lookup relationships of type RELATION to ENTITY with MBID."
;; no sanity and/or error checks
(musicbrainz-lookup entity mbid (format "%s-rels" relation)))
;; specific MBID lookup requests & subrequests (limited to 25 results?) ;; specific MBID lookup requests & subrequests (limited to 25 results?)
@ -470,12 +564,12 @@ See listenbrainz--deformatter for details."
;; lookup -> musicbrainz-lookup-area ;; lookup -> musicbrainz-lookup-area
(musicbrainz--deflookup-1 "area" (musicbrainz--deflookup-1 "area"
"| %s | [[https://musicbrainz.org/area/%s][%s]] |\n" "| %s | [[https://musicbrainz.org/area/%s][%s]] |\n"
(.name .id .id)) (.name .id .id))
;; lookup -> musicbrainz-lookup-artist ;; lookup -> musicbrainz-lookup-artist
(musicbrainz--deflookup-1 "artist" (musicbrainz--deflookup-1 "artist"
"| %s | %s | %s | [[https://musicbrainz.org/artist/%s][%s]] |\n" "| %s | %s | %s | [[https://musicbrainz.org/artist/%s][%s]] |\n"
(.name .disambiguation .type .id .id)) (.name .disambiguation .type .id .id))
;; lookup -> musicbrainz-lookup-artist-recordings ;; lookup -> musicbrainz-lookup-artist-recordings
@ -486,19 +580,19 @@ See listenbrainz--deformatter for details."
;; lookup -> musicbrainz-lookup-artist-releases ;; lookup -> musicbrainz-lookup-artist-releases
(musicbrainz--deflookup-2 "artist" "releases" (musicbrainz--deflookup-2 "artist" "releases"
"%s | %s | %s | [[https://musicbrainz.org/release/%s][%s]] |\n" "%s | %s | %s | [[https://musicbrainz.org/release/%s][%s]] |\n"
(.date .title .packaging .id .id) (.date .title .packaging .id .id)
.releases) .releases)
;; lookup -> musicbrainz-lookup-artist-release-groups ;; lookup -> musicbrainz-lookup-artist-release-groups
(musicbrainz--deflookup-2 "artist" "release-groups" (musicbrainz--deflookup-2 "artist" "release-groups"
"%s | %s | %s | [[https://musicbrainz.org/release-group/%s][%s]] |\n" "%s | %s | %s | [[https://musicbrainz.org/release-group/%s][%s]] |\n"
(.first-release-date .title .primary-type .id .id) (.first-release-date .title .primary-type .id .id)
.release-groups) .release-groups)
;; lookup -> musicbrainz-lookup-artist-works ;; lookup -> musicbrainz-lookup-artist-works
(musicbrainz--deflookup-2 "artist" "works" (musicbrainz--deflookup-2 "artist" "works"
" %s | [[https://musicbrainz.org/work/%s][%s]] |\n" " %s | [[https://musicbrainz.org/work/%s][%s]] |\n"
(.title .id .id) (.title .id .id)
.works) .works)
@ -509,69 +603,69 @@ See listenbrainz--deformatter for details."
;; lookup -> musicbrainz-lookup-collection-user-collections (requires authentication) ;; lookup -> musicbrainz-lookup-collection-user-collections (requires authentication)
(musicbrainz--deflookup-2 "collection" "user-collections" (musicbrainz--deflookup-2 "collection" "user-collections"
" %s | [[https://musicbrainz.org/collection/%s][%s]] |\n" " %s | [[https://musicbrainz.org/collection/%s][%s]] |\n"
(.name .id .id) (.name .id .id)
.collection) .collection)
;; lookup -> musicbrainz-lookup-event ;; lookup -> musicbrainz-lookup-event
(musicbrainz--deflookup-1 "event" (musicbrainz--deflookup-1 "event"
"| %s | [[https://musicbrainz.org/event/%s][%s]] |\n" "| %s | [[https://musicbrainz.org/event/%s][%s]] |\n"
(.name .id .id)) (.name .id .id))
;; lookup -> musicbrainz-lookup-genre ;; lookup -> musicbrainz-lookup-genre
(musicbrainz--deflookup-1 "genre" (musicbrainz--deflookup-1 "genre"
"| %s | [[https://musicbrainz.org/genre/%s][%s]] |\n" "| %s | [[https://musicbrainz.org/genre/%s][%s]] |\n"
(.name .id .id)) (.name .id .id))
;; lookup -> musicbrainz-lookup-instrument ;; lookup -> musicbrainz-lookup-instrument
(musicbrainz--deflookup-1 "instrument" (musicbrainz--deflookup-1 "instrument"
"| %s | %s | [[https://musicbrainz.org/instrument/%s][%s]] |\n" "| %s | %s | [[https://musicbrainz.org/instrument/%s][%s]] |\n"
(.name .type .id .id)) (.name .type .id .id))
;; lookup -> musicbrainz-lookup-label ;; lookup -> musicbrainz-lookup-label
(musicbrainz--deflookup-1 "label" (musicbrainz--deflookup-1 "label"
"| %s | %s | [[https://musicbrainz.org/label/%s][%s]] |\n" "| %s | %s | [[https://musicbrainz.org/label/%s][%s]] |\n"
(.name .disambiguation .id .id)) (.name .disambiguation .id .id))
;; lookup -> musicbrainz-lookup-label-releases ;; lookup -> musicbrainz-lookup-label-releases
(musicbrainz--deflookup-2 "label" "releases" (musicbrainz--deflookup-2 "label" "releases"
"%s | %s | [[https://musicbrainz.org/release/%s][%s]] |\n" "%s | %s | [[https://musicbrainz.org/release/%s][%s]] |\n"
(.date .title .id .id) (.date .title .id .id)
.releases) .releases)
;; lookup -> musicbrainz-lookup-place ;; lookup -> musicbrainz-lookup-place
(musicbrainz--deflookup-1 "place" (musicbrainz--deflookup-1 "place"
"| %s | [[https://musicbrainz.org/place/%s][%s]] |\n" "| %s | [[https://musicbrainz.org/place/%s][%s]] |\n"
(.name .id .id)) (.name .id .id))
;; lookup -> musicbrainz-lookup-recording ;; lookup -> musicbrainz-lookup-recording
(musicbrainz--deflookup-1 "recording" (musicbrainz--deflookup-1 "recording"
"| %s | %s | [[https://musicbrainz.org/recording/%s][%s]] |\n" "| %s | %s | [[https://musicbrainz.org/recording/%s][%s]] |\n"
(.first-release-date .title .id .id)) (.first-release-date .title .id .id))
;; lookup -> musicbrainz-lookup-recording-artists ;; lookup -> musicbrainz-lookup-recording-artists
(musicbrainz--deflookup-2 "recording" "artists" (musicbrainz--deflookup-2 "recording" "artists"
"%s | [[https://musicbrainz.org/artist/%s][%s]] |\n" "%s | [[https://musicbrainz.org/artist/%s][%s]] |\n"
(.artist.name .artist.id .artist.id) (.artist.name .artist.id .artist.id)
.artist-credit) .artist-credit)
;; lookup -> musicbrainz-lookup-recording-releases ;; lookup -> musicbrainz-lookup-recording-releases
(musicbrainz--deflookup-2 "recording" "releases" (musicbrainz--deflookup-2 "recording" "releases"
"%s | %s | %s | [[https://musicbrainz.org/release/%s][%s]] |\n" "%s | %s | %s | [[https://musicbrainz.org/release/%s][%s]] |\n"
(.date .title .packaging .id .id) (.date .title .packaging .id .id)
.releases) .releases)
;; lookup -> musicbrainz-lookup-recording-isrcs ;; lookup -> musicbrainz-lookup-recording-isrcs
(musicbrainz--deflookup-2 "recording" "isrcs" (musicbrainz--deflookup-2 "recording" "isrcs"
"%s | [[https://musicbrainz.org/isrc/%s][%s]] |\n" "%s | [[https://musicbrainz.org/isrc/%s][%s]] |\n"
(.name .id .id) (.name .id .id)
.isrcs) .isrcs)
;; lookup -> musicbrainz-lookup-recording-url-rels ;; lookup -> musicbrainz-lookup-recording-url-rels
(musicbrainz--deflookup-2 "recording" "url-rels" (musicbrainz--deflookup-2 "recording" "url-rels"
"%s | [[https://musicbrainz.org/recording/%s][%s]] |\n" "%s | [[https://musicbrainz.org/recording/%s][%s]] |\n"
(.name .id .id) (.name .id .id)
.relations) .relations)
@ -596,7 +690,7 @@ See listenbrainz--deformatter for details."
;; lookup -> musicbrainz-lookup-release-group ;; lookup -> musicbrainz-lookup-release-group
(musicbrainz--deflookup-1 "release-group" (musicbrainz--deflookup-1 "release-group"
"| %s | %s | %s | [[https://musicbrainz.org/release-group/%s][%s]] |\n" "| %s | %s | %s | [[https://musicbrainz.org/release-group/%s][%s]] |\n"
(.first-release-date .title .primary-type .id .id)) (.first-release-date .title .primary-type .id .id))
;; lookup -> musicbrainz-lookup-release-group-artists ;; lookup -> musicbrainz-lookup-release-group-artists
@ -609,17 +703,17 @@ See listenbrainz--deformatter for details."
;; lookup -> musicbrainz-lookup-series ;; lookup -> musicbrainz-lookup-series
(musicbrainz--deflookup-1 "series" (musicbrainz--deflookup-1 "series"
"| %s | [[https://musicbrainz.org/series/%s][%s]] |\n" "| %s | [[https://musicbrainz.org/series/%s][%s]] |\n"
(.name .id .id)) (.title .id .id))
;; lookup -> musicbrainz-lookup-work ;; lookup -> musicbrainz-lookup-work
(musicbrainz--deflookup-1 "work" (musicbrainz--deflookup-1 "work"
"| %s | [[https://musicbrainz.org/work/%s][%s]] |\n" "| %s | [[https://musicbrainz.org/work/%s][%s]] |\n"
(.name .id .id)) (.title .id .id))
;; lookup -> musicbrainz-lookup-url ;; lookup -> musicbrainz-lookup-url
(musicbrainz--deflookup-1 "url" (musicbrainz--deflookup-1 "url"
"| %s | [[https://musicbrainz.org/url/%s][%s]] |\n" "| %s | [[https://musicbrainz.org/url/%s][%s]] |\n"
(.name .id .id)) (.name .id .id))
@ -672,7 +766,6 @@ Optionally limit the search to TYPE results for ENTITY."
response)) response))
;;;
(provide 'musicbrainz) (provide 'musicbrainz)