From db261d954b8d4e8762460f6d1f4a4744ae952008 Mon Sep 17 00:00:00 2001 From: pan93412 Date: Fri, 11 Aug 2023 23:59:07 +0800 Subject: [PATCH 1/2] fix(lyrics-plus/netease): use PyNCMd API The old xianqiao.wang API seems like removed; therefore, I switch it to PyNCMd, which is actively maintained. --- CustomApps/lyrics-plus/ProviderNetease.js | 77 ++++++++++++++++++----- 1 file changed, 63 insertions(+), 14 deletions(-) diff --git a/CustomApps/lyrics-plus/ProviderNetease.js b/CustomApps/lyrics-plus/ProviderNetease.js index 91c57d8945..4a9f66d895 100644 --- a/CustomApps/lyrics-plus/ProviderNetease.js +++ b/CustomApps/lyrics-plus/ProviderNetease.js @@ -1,26 +1,75 @@ -const ProviderNetease = (function () { - const requestHeader = { - "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:93.0) Gecko/20100101 Firefox/93.0" - }; +/** + * @typedef {{ + * result: { + * songs: { + * name: string, + * id: number, + * dt: number, // duration in ms + * al: { // album + * name: string, + * }, + * }[], + * }, + * }} SearchResponse + * + * @typedef {{ + * title: string, + * artist: string, + * album: string, + * duration: number, + * }} Info + */ - async function findLyrics(info) { - const searchURL = `https://music.xianqiao.wang/neteaseapiv2/search?limit=10&type=1&keywords=`; - const lyricURL = `https://music.xianqiao.wang/neteaseapiv2/lyric?id=`; +const ProviderNetease = (function () { + /** + * Search with PyNCM api. + * + * @param {Info} info + * @throw "Cannot find track" + */ + async function search(info) { + const searchURL = `https://pyncmd.apis.imouto.in/api/pyncm?module=cloudsearch&method=GetSearchResult&keyword=`; const cleanTitle = Utils.removeExtraInfo(Utils.removeSongFeat(Utils.normalize(info.title))); const finalURL = searchURL + encodeURIComponent(`${cleanTitle} ${info.artist}`); - const searchResults = await CosmosAsync.get(finalURL, null, requestHeader); + /** @type {SearchResponse} */ + const searchResults = await Spicetify.CosmosAsync.get(finalURL); const items = searchResults.result.songs; - if (!items?.length) { - throw "Cannot find track"; + + // Find the best match. + for (const song of items) { + const expectedDuration = info.duration; + const actualDuration = song.dt; + + const expectedAlbumName = Utils.normalize(info.album); + const actualAlbumName = Utils.normalize(song.al.name); + + if (actualAlbumName == expectedAlbumName || Math.abs(expectedDuration - actualDuration) < 1000) { + return song; + } } - const album = Utils.capitalize(info.album); - let itemId = items.findIndex(val => Utils.capitalize(val.album.name) === album || Math.abs(info.duration - val.duration) < 1000); - if (itemId === -1) throw "Cannot find track"; + throw "Cannot find track"; + } + + /** + * @param {Info} info + * + * @returns {{ + * lrc: { + * lyric: string, + * klyric: undefined, // unimplemented + * }, + * }} + */ + async function findLyrics(info) { + const lyricURL = `https://pyncmd.apis.imouto.in/api/pyncm?module=track&method=GetTrackLyrics&song_id=`; + + const searchResponse = await search(info); + const songID = searchResponse.id; - return await CosmosAsync.get(lyricURL + items[itemId].id, null, requestHeader); + return CosmosAsync.get(lyricURL + songID); } const creditInfo = [ From 2ec3abc1655ee434e8b13a43ff5cfaf14d0cc22e Mon Sep 17 00:00:00 2001 From: pan93412 Date: Sat, 12 Aug 2023 00:00:27 +0800 Subject: [PATCH 2/2] feat(lyrics-plus/netease): normalize album name before comparing --- CustomApps/lyrics-plus/ProviderNetease.js | 6 ++-- CustomApps/lyrics-plus/Utils.js | 37 +++++++++++++++++++++++ 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/CustomApps/lyrics-plus/ProviderNetease.js b/CustomApps/lyrics-plus/ProviderNetease.js index 4a9f66d895..53902eb03a 100644 --- a/CustomApps/lyrics-plus/ProviderNetease.js +++ b/CustomApps/lyrics-plus/ProviderNetease.js @@ -42,8 +42,10 @@ const ProviderNetease = (function () { const expectedDuration = info.duration; const actualDuration = song.dt; - const expectedAlbumName = Utils.normalize(info.album); - const actualAlbumName = Utils.normalize(song.al.name); + // normalized expected album name + const neAlbumName = Utils.normalize(info.album); + const expectedAlbumName = Utils.containsHanCharacter(neAlbumName) ? await Utils.toSimplifiedChinese(neAlbumName) : neAlbumName; + const actualAlbumName = Utils.normalize(song.al.name); // usually in Simplified Chinese if (actualAlbumName == expectedAlbumName || Math.abs(expectedDuration - actualDuration) < 1000) { return song; diff --git a/CustomApps/lyrics-plus/Utils.js b/CustomApps/lyrics-plus/Utils.js index f14a96028c..7195b11a01 100644 --- a/CustomApps/lyrics-plus/Utils.js +++ b/CustomApps/lyrics-plus/Utils.js @@ -38,6 +38,43 @@ class Utils { return result.replace(/\s+/g, " ").trim(); } + /** + * Check if the specified string contains Han character. + * + * @param {string} s + */ + static containsHanCharacter(s) { + const hanRegex = /\p{Script=Han}/u; + return hanRegex.test(s); + } + + /** + * Singleton Translator instance for {@link toSimplifiedChinese}. + * + * @type {Translator} + */ + static #translator = null; + + /** + * Convert all Han characters to Simplified Chinese. + * + * Choosing Simplified Chinese makes the converted result more accurate, + * as the conversion from SC to TC may have multiple possibilities, + * while the conversion from TC to SC usually has only one possibility. + * + * @param {string} s + * @returns {Promise} + */ + static async toSimplifiedChinese(s) { + // create a singleton Translator instance + if (!Utils.#translator) { + Utils.#translator = new Translator("zh"); + } + + // translate it to Simplified Chinese + return Utils.#translator.convertChinese(s, "tw", "cn"); + } + static removeSongFeat(s) { return ( s