From 61d4d25f719968fdfd96a124749c26a18543b420 Mon Sep 17 00:00:00 2001 From: eli Date: Fri, 4 Feb 2022 13:37:20 +0800 Subject: [PATCH 1/6] fix: pass correct query the original method always passes unmodified query regardless of the value of 'edit' --- lyrics-fetcher-genius.el | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lyrics-fetcher-genius.el b/lyrics-fetcher-genius.el index df10e02..f3f9f51 100644 --- a/lyrics-fetcher-genius.el +++ b/lyrics-fetcher-genius.el @@ -113,9 +113,9 @@ When EDIT is non-nil, edit the query in minibuffer before search." (defun lyrics-fetcher-genius--maybe-edit-query (query edit) "If EDIT is non-nil, edit QUERY in minibuffer." - (when edit - (read-from-minibuffer "Query: " query)) - query) + (if edit + (read-from-minibuffer "Query: " query) + query)) (defun lyrics-fetcher-genius--format-query (track) "Format track to genius.com query. From e5d636e01ec392fccb414c9c5b4a3f54b03b6ca5 Mon Sep 17 00:00:00 2001 From: eli Date: Fri, 4 Feb 2022 14:10:21 +0800 Subject: [PATCH 2/6] feat: add neteasecloud backend Emms supports real-time lyrics(.lrc format) which can be showed in minibuffer or modeline. It would be better for lyrics-fetcher to fetch `.lrc` format lyrics. Luckily, I find `music.163.com` offers an API to return such format lyrics, for instance: https://music.163.com/api/song/lyric?id=35804609&lv=1&kv=1&tv=-1, in which '35804609' is the song id you query. So I writes a new backend for 'lyrics-fetcher' to get lrc format lyrics, and the following are my config ``` (setq lyrics-fetcher-fetch-method #'lyrics-fetcher-neteasecloud-do-search) (setq lyrics-fetcher-format-file-name-method #'lyrics-fetcher-neteasecloud-format-file-name) (setq lyrics-fetcher-format-song-name-method #'lyrics-fetcher-neteasecloud-format-song-name) (setq lyrics-fetcher-lyrics-file-extension ".lrc") ``` P.S. This is my first package writing and first pr, so if I did something wrong, please tell me. --- lyrics-fetcher-neteasecloud.el | 216 +++++++++++++++++++++++++++++++++ 1 file changed, 216 insertions(+) create mode 100644 lyrics-fetcher-neteasecloud.el diff --git a/lyrics-fetcher-neteasecloud.el b/lyrics-fetcher-neteasecloud.el new file mode 100644 index 0000000..ae5101b --- /dev/null +++ b/lyrics-fetcher-neteasecloud.el @@ -0,0 +1,216 @@ +;;; lyrics-fetcher-neteasecloud.el --- Fetch lyrics from music.163.com -*- lexical-binding: t -*- + +;; Copyright (C) 2021 Korytov Pavel +;; Copyright (C) 2021 Syohei YOSHIDA +;; Copyright (C) 2021 Eli Qian +;; Copyright (C) 2014-2021 Free Software Foundation, Inc. + +;; Author: Eli Qian +;; Maintainer: Korytov Pavel +;; Homepage: https://github.com/SqrtMinusOne/lyrics-fetcher.el + +;; This file is NOT part of GNU Emacs. + +;; This program 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 of the License, or +;; (at your option) any later version. + +;; This program 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 this program. If not, see . + +;;; Commentary: + +;; Fetch song lyrics from genius.com. + +;;; Code: +(require 'request) +(require 'cl-lib) +(require 'json) +(require 'seq) +(require 'shr) +(require 'f) +(require 'dom) + +(defcustom lyrics-fetcher-neteasecloud-strip-parens-from-query t + "Strip parens from the query. + +I've noticed that these often break the search, e.g. when +searching \"Song (feat. Artist)\"" + :type 'boolean + :group 'lyrics-fetcher) + +(defun lyrics-fetcher-neteasecloud-do-search (track callback &optional sync edit) + "Perform a lyrics search on 'music.163.com'. + +The flow is as follows: +1. Send a POST /search request with a text query +2. Pick the first result (or prompt user if SYNC is non-nil) +3. Fetch lyrics +4. Call CALLBACK with the resulting lyrics string + +TRACK should be EMMS-compatible alist or string, take a look at +`lyrics-fetcher-neteasecloud--format-query'. If the search is +successful, CALLBACK will be called with the result. + +If SYNC is non-nil, perform request synchronously and ask the +user to pick the matching search result. + +When EDIT is non-nil, edit the query in minibuffer before search. +Genius usually struggles to find song if there is extra +information in the title. +" + (lyrics-fetcher-neteasecloud--do-query + track + (lambda (data) + (lyrics-fetcher-neteasecloud--fetch-lyrics + (lyrics-fetcher-neteasecloud--get-song-id data sync) + callback + sync)) + sync + edit)) + +(defun lyrics-fetcher-neteasecloud--fetch-lyrics (song-id callback &optional sync) + "Fetch lyrics from 'music.163.com' page at URL and call CALLBACK with the result. + +If SYNC is non-nil, the request will be performed synchronously." + (message "Getting lyrics from NeteaseCloud API...") + (request + (format "http://music.163.com/api/song/lyric?id=%s&lv=1&kv=1&tv=-1" song-id) + :parser 'json-read + :sync sync + :success (cl-function + (lambda (&key data &allow-other-keys) + (funcall callback (alist-get 'lyric (alist-get 'lrc data))))) + :error + (cl-function + (lambda (&key error-thrown &allow-other-keys) + (message "Error!: %S" error-thrown))) + )) + +(defun lyrics-fetcher-neteasecloud--do-query (track callback &optional sync edit) + "Perform a song search on 'music.163.com'. + +TRACK should be EMMS-compatible alist or string, take a look at +`lyrics-fetcher-neteasecloud--format-query'. If the search is +successful, CALLBACK will be called with the result. + +SYNC determines whether the request is synchronous. The parameter +is useful when it is necessary to ask the user for something right +after the request. + +When EDIT is non-nil, edit the query in minibuffer before search." + (message "Sending a query to NeteaseCloud API...") + (request "http://music.163.com/api/search/get/" + :type "POST" + :data `(("s" . ,(lyrics-fetcher-neteasecloud--maybe-edit-query + (lyrics-fetcher-neteasecloud--format-query track) + edit)) + ("limit" . "10") + ("type" . "1") + ("offset" . "0")) + :parser 'json-read + :sync sync + :success (cl-function + (lambda (&key data &allow-other-keys) + (funcall callback data))) + :error (cl-function + (lambda (&key error-thrown &allow-other-keys) + (message "Error!: %S" error-thrown))) + )) + +(defun lyrics-fetcher-neteasecloud--maybe-edit-query (query edit) + "If EDIT is non-nil, edit QUERY in minibuffer." + (if edit + (read-from-minibuffer "Query: " query) + query)) + +(defun lyrics-fetcher-neteasecloud--format-query (track) + "Format track to 'music.163.com' query. + +When `lyrics-fetcher-neteasecloud-strip-parens-from-query' is non-nil, +remove all the text in parens from the query, +for instance (feat. someone). + +TRACK should either be a string or an EMMS-compatible alist, which +contains `info-artist' or `info-title'" + (if (stringp track) + track + (let ((query (concat + (cdr (assoc 'info-title track)) + " " + (cdr (assoc 'info-artist track))))) + (when lyrics-fetcher-neteasecloud-strip-parens-from-query + (setq query (replace-regexp-in-string + (rx (or (: "(" (* nonl) ")") + (: "[" (* nonl) "]"))) + "" query))) + query))) + +(defun lyrics-fetcher-neteasecloud--get-song-id (data &optional ask) + "Retrieve a song id from the 'music.163.com' response DATA. + +If ASK is non-nil, prompt the user for a choice, otherwise select the +first song." + (if (/= 200 (alist-get 'code data)) + (error "ERROR: %s" (alist-get 'code data)) + (let* ((results (alist-get 'songs (alist-get 'result data)))) + (if (seq-empty-p results) + (error "ERROR: no results!") + (cdr + (if ask + (let ((results-songs-for-select + (mapcar + (lambda (entry) + (cons (lyrics-fetcher-neteasecloud--format-song-title entry) + (assoc 'id entry))) + results))) + (cdr + (assoc + (completing-read + "Pick a result: " + results-songs-for-select + nil t) + results-songs-for-select))) + (assoc 'id (aref results 0)) + )))))) + +(defun lyrics-fetcher-neteasecloud--format-song-title (entry) + "Convert a 'music.163.com' search ENTRY to a string, which can be used in selection." + (format "%s by %s" + (cdr (assoc 'name entry)) + (cdr (assoc 'name (aref (alist-get 'artists entry) 0))) + )) + +(defun lyrics-fetcher-neteasecloud-format-file-name (track) + "'Emms' requires lyrics files' name should be the same as their tracks' name except extensions" + (if (stringp track) + (substring + (lyrics-fetcher--prepare-string track) + 0 + (min (length track) 250)) + (let ((full-name (emms-track-get track 'name))) + (emms-replace-regexp-in-string + (concat "\\." (file-name-extension full-name) "\\'") + "" + (file-name-nondirectory full-name)) + ))) + +(defun lyrics-fetcher-neteasecloud-format-song-name (track) + "Format TRACK to a human-readable form. + +TRACK should be either a string or EMMS alist." + (if (stringp track) + track + (format "%s %s" + (cdr (assoc 'info-title track)) + (cdr (assoc 'info-artist track)) + ))) + +(provide 'lyrics-fetcher-neteasecloud) +;;; lyrics-fetcher-neteasecloud.el ends here From 3613646387d2e7ec7525da3e75a5507c53bf3fc3 Mon Sep 17 00:00:00 2001 From: eli Date: Fri, 4 Feb 2022 16:34:09 +0800 Subject: [PATCH 3/6] chore: remove unused packages --- lyrics-fetcher-neteasecloud.el | 3 --- 1 file changed, 3 deletions(-) diff --git a/lyrics-fetcher-neteasecloud.el b/lyrics-fetcher-neteasecloud.el index ae5101b..b23b80e 100644 --- a/lyrics-fetcher-neteasecloud.el +++ b/lyrics-fetcher-neteasecloud.el @@ -33,9 +33,6 @@ (require 'cl-lib) (require 'json) (require 'seq) -(require 'shr) -(require 'f) -(require 'dom) (defcustom lyrics-fetcher-neteasecloud-strip-parens-from-query t "Strip parens from the query. From 3dfe79f34cbdd28884e096bfbb18c026d5c33fef Mon Sep 17 00:00:00 2001 From: eli Date: Fri, 4 Feb 2022 16:35:21 +0800 Subject: [PATCH 4/6] chore: update docstring --- lyrics-fetcher-neteasecloud.el | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/lyrics-fetcher-neteasecloud.el b/lyrics-fetcher-neteasecloud.el index b23b80e..3a707ef 100644 --- a/lyrics-fetcher-neteasecloud.el +++ b/lyrics-fetcher-neteasecloud.el @@ -60,8 +60,7 @@ user to pick the matching search result. When EDIT is non-nil, edit the query in minibuffer before search. Genius usually struggles to find song if there is extra -information in the title. -" +information in the title." (lyrics-fetcher-neteasecloud--do-query track (lambda (data) @@ -75,7 +74,9 @@ information in the title. (defun lyrics-fetcher-neteasecloud--fetch-lyrics (song-id callback &optional sync) "Fetch lyrics from 'music.163.com' page at URL and call CALLBACK with the result. -If SYNC is non-nil, the request will be performed synchronously." +SONG-ID is a sequence of number which indicates a song, it can be +returned by 'lyrics-fetcher-neteasecloud--get-song-id' If SYNC is +non-nil, the request will be performed synchronously." (message "Getting lyrics from NeteaseCloud API...") (request (format "http://music.163.com/api/song/lyric?id=%s&lv=1&kv=1&tv=-1" song-id) @@ -185,7 +186,9 @@ first song." )) (defun lyrics-fetcher-neteasecloud-format-file-name (track) - "'Emms' requires lyrics files' name should be the same as their tracks' name except extensions" + "TRACK should be either a string or EMMS alist. +'Emms' requires lyrics files' name should be the same as their +tracks' name except extensions." (if (stringp track) (substring (lyrics-fetcher--prepare-string track) From 48b6d8e8acf253482aeb735308d6e2a655ebbc31 Mon Sep 17 00:00:00 2001 From: eli Date: Fri, 4 Feb 2022 16:58:51 +0800 Subject: [PATCH 5/6] chore: remove trailing parentheses --- lyrics-fetcher-neteasecloud.el | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/lyrics-fetcher-neteasecloud.el b/lyrics-fetcher-neteasecloud.el index 3a707ef..1e0231f 100644 --- a/lyrics-fetcher-neteasecloud.el +++ b/lyrics-fetcher-neteasecloud.el @@ -83,13 +83,12 @@ non-nil, the request will be performed synchronously." :parser 'json-read :sync sync :success (cl-function - (lambda (&key data &allow-other-keys) - (funcall callback (alist-get 'lyric (alist-get 'lrc data))))) + (lambda (&key data &allow-other-keys) + (funcall callback (alist-get 'lyric (alist-get 'lrc data))))) :error (cl-function (lambda (&key error-thrown &allow-other-keys) - (message "Error!: %S" error-thrown))) - )) + (message "Error!: %S" error-thrown))))) (defun lyrics-fetcher-neteasecloud--do-query (track callback &optional sync edit) "Perform a song search on 'music.163.com'. @@ -119,8 +118,7 @@ When EDIT is non-nil, edit the query in minibuffer before search." (funcall callback data))) :error (cl-function (lambda (&key error-thrown &allow-other-keys) - (message "Error!: %S" error-thrown))) - )) + (message "Error!: %S" error-thrown))))) (defun lyrics-fetcher-neteasecloud--maybe-edit-query (query edit) "If EDIT is non-nil, edit QUERY in minibuffer." @@ -175,15 +173,13 @@ first song." results-songs-for-select nil t) results-songs-for-select))) - (assoc 'id (aref results 0)) - )))))) + (assoc 'id (aref results 0)))))))) (defun lyrics-fetcher-neteasecloud--format-song-title (entry) "Convert a 'music.163.com' search ENTRY to a string, which can be used in selection." (format "%s by %s" (cdr (assoc 'name entry)) - (cdr (assoc 'name (aref (alist-get 'artists entry) 0))) - )) + (cdr (assoc 'name (aref (alist-get 'artists entry) 0))))) (defun lyrics-fetcher-neteasecloud-format-file-name (track) "TRACK should be either a string or EMMS alist. @@ -198,8 +194,7 @@ tracks' name except extensions." (emms-replace-regexp-in-string (concat "\\." (file-name-extension full-name) "\\'") "" - (file-name-nondirectory full-name)) - ))) + (file-name-nondirectory full-name))))) (defun lyrics-fetcher-neteasecloud-format-song-name (track) "Format TRACK to a human-readable form. @@ -209,8 +204,7 @@ TRACK should be either a string or EMMS alist." track (format "%s %s" (cdr (assoc 'info-title track)) - (cdr (assoc 'info-artist track)) - ))) + (cdr (assoc 'info-artist track))))) (provide 'lyrics-fetcher-neteasecloud) ;;; lyrics-fetcher-neteasecloud.el ends here From 1ac35c95b062ac8493f471a83535468fe81ee590 Mon Sep 17 00:00:00 2001 From: eli Date: Fri, 4 Feb 2022 16:59:23 +0800 Subject: [PATCH 6/6] chore: remove FSF Copyright --- lyrics-fetcher-neteasecloud.el | 1 - 1 file changed, 1 deletion(-) diff --git a/lyrics-fetcher-neteasecloud.el b/lyrics-fetcher-neteasecloud.el index 1e0231f..5ce903a 100644 --- a/lyrics-fetcher-neteasecloud.el +++ b/lyrics-fetcher-neteasecloud.el @@ -3,7 +3,6 @@ ;; Copyright (C) 2021 Korytov Pavel ;; Copyright (C) 2021 Syohei YOSHIDA ;; Copyright (C) 2021 Eli Qian -;; Copyright (C) 2014-2021 Free Software Foundation, Inc. ;; Author: Eli Qian ;; Maintainer: Korytov Pavel