]> git.vanrenterghem.biz Git - musicbrainz.git/blobdiff - listenbrainz.el
Support individual historic listens submittion.
[musicbrainz.git] / listenbrainz.el
index 02295510799734814ec4c8aa34e704b13c9fc356..5ebf40adf7361edd8efca2c5ea4e42839f77d156 100644 (file)
@@ -2,9 +2,10 @@
 
 ;; Copyright 2023 FoAM
 ;;
-;; Author: nik gaffney <nik@fo.am>
+;; Author: nik gaffney <nik@fo.am>, Frederik Vanrenterghem <frederik@vanrenterghem.biz>
 ;; Created: 2023-05-05
-;; Version: 0.1
+;; Updated: 2025-01-25
+;; Version: 0.2fv
 ;; Package-Requires: ((emacs "27.1") (request "0.3"))
 ;; Keywords: music, scrobbling, multimedia
 ;; URL: https://github.com/zzkt/listenbrainz
 
 ;;; Commentary:
 
-;; - listen & submit metadata to ListenBrainz
-;; - partial & incomplete
-;; - no error checks
-;; - sync -> async
+;; An interface to ListenBrainz, a project to store a record of the music that
+;; you listen to. The listening data, can be used to provide statistics,
+;; recommendations and general exploration.
+;;
+;; 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:
@@ -104,7 +114,6 @@ All timestamps used in ListenBrainz are UNIX epoch timestamps in UTC."
 ;;
 ;;;; ; ;; ;
 
-
 (defmacro listenbrainz--deformatter (name format-string format-args alist)
   "Generate function with NAME to format data returned from an API call.
 The function has the name `listenbrainz--format-NAME`.
@@ -216,7 +225,6 @@ macroexpands to something like ->
 ;;
 ;;; ; ;; ; ;   ;
 
-;;;###autoload
 (defun listenbrainz-validate-token (token)
   "Check if TOKEN is valid. Return a username or nil."
   (message "listenbrainz: checking token %s" token)
@@ -255,7 +263,8 @@ macroexpands to something like ->
              :sync t
              :success (cl-function
                        (lambda (&key data &allow-other-keys)
-                         (message "Listens for user: %s" username)))))))
+                         (message "Listens for user: %s\n%s" username
+                                  (if data data ""))))))))
     (princ (listenbrainz--format-listens response))))
 
 
@@ -272,7 +281,8 @@ macroexpands to something like ->
              :sync t
              :success (cl-function
                        (lambda (&key data &allow-other-keys)
-                         (message "User playing now: %s" username)))))))
+                         (message "User playing now: %s\n%s" username
+                                  (if data data ""))))))))
     (princ (listenbrainz--format-playing response))))
 
 
@@ -280,12 +290,13 @@ macroexpands to something like ->
 ;; - https://listenbrainz.readthedocs.io/en/production/dev/api-usage/#submitting-listens
 ;; - https://listenbrainz.readthedocs.io/en/production/dev/json/#json-doc
 
-(defun listenbrainz-submit-listen (type artist track &optional release)
+(defun listenbrainz-submit-listen (type artist track &optional release timestamp)
   "Submit listening data to ListenBrainz.
 - listen TYPE (string) either \='single\=', \='import\=' or \='playing_now\='
 - ARTIST name (string)
 - TRACK title (string)
-- RELEASE title (string) also  album title."
+- RELEASE title (string) also  album title.
+- TIMESTAMP time (time) track was listened to"
   (message "listenbrainz: submitting %s - %s - %s" artist track release)
   (let* ((json-null "")
          (now (listenbrainz-timestamp))
@@ -297,6 +308,7 @@ macroexpands to something like ->
                          (remove nil
                                  (list
                                   (when (string= type "single") (cons "listened_at" now))
+                                 (when (string= type "import") (cons "listened_at" (listenbrainz-timestamp timestamp)))
                                   (list "track_metadata"
                                         (cons "artist_name" artist)
                                         (cons "track_name" track)
@@ -323,6 +335,10 @@ macroexpands to something like ->
   "Submit data for track (ARTIST TRACK and optional RELEASE) playing now."
   (listenbrainz-submit-listen "playing_now" artist track (when release release)))
 
+;;;###autoload
+(defun listenbrainz-submit-historic-listen (artist track timestamp &optional release)
+  "Submit data for track (ARTIST TRACK and optional RELEASE) heard at TIMESTAMP."
+  (listenbrainz-submit-listen "import" artist track (if release release "") timestamp))
 
 ;;; ;; ;; ;  ; ;   ;  ;      ;
 ;;
@@ -351,7 +367,8 @@ possible values are week, month, year, all_time, defaults to all_time."
              :sync t
              :success (cl-function
                        (lambda (&key data &allow-other-keys)
-                         (message "Top recordings for user: %s" username)))))))
+                         (message "Top recordings for user: %s\n%s" username
+                                  (if data data ""))))))))
     (princ (listenbrainz--format-stats-2 response))))
 
 
@@ -374,7 +391,8 @@ possible values are week, month, year, all_time, defaults to all_time."
              :sync t
              :success (cl-function
                        (lambda (&key data &allow-other-keys)
-                         (message "Top releases for user: %s" username)))))))
+                         (message "Top releases for user: %s\n%s" username
+                                  (if data data ""))))))))
     (princ (listenbrainz--format-stats-0 response))))
 
 
@@ -397,7 +415,8 @@ possible values are week, month, year, all_time, defaults to all_time."
              :sync t
              :success (cl-function
                        (lambda (&key data &allow-other-keys)
-                         (message "Top artists for user: %s" username)))))))
+                         (message "Top artists for user: %s\n%s" username
+                                  (if data data ""))))))))
     (princ (listenbrainz--format-stats-1 response))))
 
 
@@ -423,10 +442,11 @@ OUTPUT format can be either `list' (default) or `graph'."
              :sync t
              :success (cl-function
                        (lambda (&key data &allow-other-keys)
-                         (message "Followers for %s" username)))))))
+                         (message "Followers for %s\n%s" username
+                                  (if data data ""))))))))
     (if (string= "graph" output)
-         (princ (listenbrainz--format-followers-graph response))
-         (princ (listenbrainz--format-followers-list response)))))
+        (princ (listenbrainz--format-followers-graph response))
+        (princ (listenbrainz--format-followers-list response)))))
 
 ;;;###autoload
 (defun listenbrainz-following (username)
@@ -441,7 +461,8 @@ OUTPUT format can be either `list' (default) or `graph'."
              :sync t
              :success (cl-function
                        (lambda (&key data &allow-other-keys)
-                         (message "Users %s is following" username)))))))
+                         (message "Users %s is following\n%s" username
+                                  (if data data ""))))))))
     (princ (listenbrainz--format-following response))))