From 3c3bfea9e0ba86ea767220eef72b82e5bf06afbd Mon Sep 17 00:00:00 2001 From: Lseoksee Date: Mon, 24 Feb 2025 18:21:57 +0900 Subject: [PATCH 1/6] popupLyrics Caching & refresh token --- Extensions/popupLyrics.js | 148 ++++++++++++++++++++++++++++++-------- 1 file changed, 117 insertions(+), 31 deletions(-) diff --git a/Extensions/popupLyrics.js b/Extensions/popupLyrics.js index abde9ec914..3dc94b6b99 100644 --- a/Extensions/popupLyrics.js +++ b/Extensions/popupLyrics.js @@ -25,6 +25,8 @@ if (!navigator.serviceWorker) { PopupLyrics(); } +let CACHE = {}; + function PopupLyrics() { const { Player, CosmosAsync, LocalStorage, ContextMenu } = Spicetify; @@ -423,7 +425,7 @@ function PopupLyrics() { Player.addEventListener("songchange", updateTrack); - async function updateTrack() { + async function updateTrack(refresh = false) { if (!lyricVideoIsOpen) { return; } @@ -443,20 +445,26 @@ function PopupLyrics() { uri: Player.data.item.uri, }; - for (const name of userConfigs.servicesOrder) { - const service = userConfigs.services[name]; - if (!service.on) continue; - sharedData = { lyrics: [] }; + if (CACHE?.[info.uri]?.lyrics?.length && !refresh) { + sharedData = CACHE[info.uri]; + } else { + for (const name of userConfigs.servicesOrder) { + const service = userConfigs.services[name]; + if (!service.on) continue; + sharedData = { lyrics: [] }; + + try { + const data = await service.call(info); + console.log(data); + sharedData = data; + CACHE[info.uri] = sharedData; - try { - const data = await service.call(info); - console.log(data); - sharedData = data; - if (!sharedData.error) { - return; + if (!sharedData.error) { + return; + } + } catch (err) { + sharedData = { error: "No lyrics" }; } - } catch (err) { - sharedData = { error: "No lyrics" }; } } } @@ -859,6 +867,27 @@ button.switch.small { height: 22px; padding: 6px; } +button.btn { + font-weight: 700; + display: block; + background-color: rgba(var(--spice-rgb-shadow), .7); + border-radius: 500px; + transition-duration: 33ms; + transition-property: background-color, border-color, color, box-shadow, filter, transform; + padding-inline: 15px; + border: 1px solid #727272; + color: var(--spice-text); + min-block-size: 32px; + cursor: pointer; +} +button.btn:hover { + transform: scale(1.04); + border-color: var(--spice-text); +} +button.btn:disabled { + opacity: 0.5; + cursor: not-allowed; +} #popup-config-container select { color: var(--spice-text); background: rgba(var(--spice-rgb-shadow), .7); @@ -975,7 +1004,7 @@ button.switch.small { const id = el.dataset.id; userConfigs.services[id].on = state; LocalStorage.set(`popup-lyrics:services:${id}:on`, state); - updateTrack(); + updateTrack(true); } function posCallback(el, dir) { @@ -990,19 +1019,11 @@ button.switch.small { LocalStorage.set("popup-lyrics:services-order", JSON.stringify(userConfigs.servicesOrder)); stackServiceElements(); - updateTrack(); - } - - function tokenChangeCallback(el, inputEl) { - const newVal = inputEl.value; - const id = el.dataset.id; - userConfigs.services[id].token = newVal; - LocalStorage.set(`popup-lyrics:services:${id}:token`, newVal); - updateTrack(); + updateTrack(true); } for (const name of userConfigs.servicesOrder) { - userConfigs.services[name].element = createServiceOption(name, userConfigs.services[name], switchCallback, posCallback, tokenChangeCallback); + userConfigs.services[name].element = createServiceOption(name, userConfigs.services[name], switchCallback, posCallback); } stackServiceElements(); @@ -1085,7 +1106,76 @@ button.switch.small { return container; } - function createServiceOption(id, defaultVal, switchCallback, posCallback, tokenCallback) { + function createButton(defaultValue, callback) { + const container = document.createElement("button"); + container.innerHTML = defaultValue; + container.className = "btn"; + + container.onclick = () => { + callback(); + }; + + return container; + } + + function createTextfield(defaultValue, placeholder, callback) { + const container = document.createElement("input"); + container.placeholder = placeholder; + container.value = defaultValue; + + container.onchange = (e) => { + callback(e.target.value); + }; + + return container; + } + + function musixmatchTokenElements(defaultVal, id) { + const button = createButton("Refresh token", clickRefresh); + button.style.marginTop = "10px"; + const textfield = createTextfield(defaultVal.token, `Place your ${id} token here`, changeTokenfield); + textfield.style.marginTop = "10px"; + + function clickRefresh() { + button.innerHTML = "Refreshing token..."; + button.disabled = true; + + Spicetify.CosmosAsync.get("https://apic-desktop.musixmatch.com/ws/1.1/token.get?app_id=web-desktop-app-v1.0", null, { + authority: "apic-desktop.musixmatch.com", + }) + .then(({ message: response }) => { + if (response.header.status_code === 200 && response.body.user_token) { + button.innerHTML = "Token refreshed"; + textfield.value = response.body.user_token; + textfield.dispatchEvent(new Event("change")); + } else if (response.header.status_code === 401) { + button.innerHTML = "Too many attempts"; + } else { + button.innerHTML = "Failed to refresh token"; + console.error("Failed to refresh token", response); + } + button.disabled = false; + }) + .catch((error) => { + button.innerHTML = "Failed to refresh token"; + console.error("Failed to refresh token", error); + button.disabled = false; + }); + } + + function changeTokenfield(value) { + userConfigs.services.musixmatch.token = value; + LocalStorage.set(`popup-lyrics:services:musixmatch:token`, value); + updateTrack(true); + } + + const container = document.createElement("div"); + container.append(button); + container.append(textfield); + return container; + } + + function createServiceOption(id, defaultVal, switchCallback, posCallback) { const name = id.replace(/^./, (c) => c.toUpperCase()); const container = document.createElement("div"); @@ -1113,12 +1203,8 @@ button.switch.small { ${defaultVal.desc}`; - if (defaultVal.token !== undefined) { - const input = document.createElement("input"); - input.placeholder = `Place your ${id} token here`; - input.value = defaultVal.token; - input.onchange = () => tokenCallback(container, input); - container.append(input); + if (id === "musixmatch") { + container.append(musixmatchTokenElements(defaultVal)); } const [up, down, slider] = container.querySelectorAll("button"); From c0cbc9445cc62150428cb9f190bfd559a5cd4d02 Mon Sep 17 00:00:00 2001 From: Lseoksee Date: Mon, 24 Feb 2025 23:20:00 +0900 Subject: [PATCH 2/6] add token refrash --- Extensions/popupLyrics.js | 136 ++++++++++++++++++++++++++++++-------- 1 file changed, 107 insertions(+), 29 deletions(-) diff --git a/Extensions/popupLyrics.js b/Extensions/popupLyrics.js index 3dc94b6b99..9ee5b3be5a 100644 --- a/Extensions/popupLyrics.js +++ b/Extensions/popupLyrics.js @@ -326,7 +326,7 @@ function PopupLyrics() { musixmatch: { on: boolLocalStorage("popup-lyrics:services:musixmatch:on"), call: LyricProviders.fetchMusixmatch, - desc: `Fully compatible with Spotify. Requires a token that can be retrieved from the official Musixmatch app. Follow instructions on Spicetify Docs.`, + desc: `Fully compatible with Spotify. Requires a token that can be retrieved from the official Musixmatch app. If you have problems with retrieving lyrics, try refreshing the token by clicking Refresh Token button.`, token: LocalStorage.get("popup-lyrics:services:musixmatch:token") || "2005218b74f939209bda92cb633c7380612e14cb7fe92dcd6a780f", }, spotify: { @@ -423,7 +423,9 @@ function PopupLyrics() { let sharedData = {}; - Player.addEventListener("songchange", updateTrack); + Player.addEventListener("songchange", () => { + updateTrack(); + }); async function updateTrack(refresh = false) { if (!lyricVideoIsOpen) { @@ -823,11 +825,20 @@ function PopupLyrics() { function openConfig(event) { event.preventDefault(); - if (!configContainer) { + + // Reset on reopen + if (configContainer) { + resetTokenButton(configContainer); + } else { configContainer = document.createElement("div"); configContainer.id = "popup-config-container"; const style = document.createElement("style"); style.innerHTML = ` +.setting-row { + display: flex; + justify-content: space-between; + align-items: center; +} .setting-row::after { content: ""; display: table; @@ -839,13 +850,16 @@ function PopupLyrics() { align-items: center; } .setting-row .col.description { - float: left; padding-right: 15px; cursor: default; + width: 50%; } .setting-row .col.action { - float: right; - text-align: right; + justify-content: flex-end; + width: 50%; +} +.popup-config-col-margin { + margin-top: 10px; } button.switch { align-items: center; @@ -974,6 +988,13 @@ button.btn:disabled { userConfigs.delay = Number(state); LocalStorage.set("popup-lyrics:delay", state); }); + const clearCache = descriptiveElement( + createButton("Clear Memory Cache", "Clear Memory Cache", () => { + CACHE = {}; + updateTrack(); + }), + "Loaded lyrics are cached in memory for faster reloading. Press this button to clear the cached lyrics from memory without restarting Spotify." + ); const serviceHeader = document.createElement("h2"); serviceHeader.innerText = "Services"; @@ -1027,7 +1048,20 @@ button.btn:disabled { } stackServiceElements(); - configContainer.append(style, optionHeader, smooth, center, cover, blurSize, fontSize, ratio, delay, serviceHeader, serviceContainer); + configContainer.append( + style, + optionHeader, + smooth, + center, + cover, + blurSize, + fontSize, + ratio, + delay, + clearCache, + serviceHeader, + serviceContainer + ); } Spicetify.PopupModal.display({ title: "Popup Lyrics", @@ -1105,36 +1139,82 @@ button.btn:disabled { return container; } + function createButton(name = null, defaultValue, callback) { + let container; + + if (name) { + container = document.createElement("div"); + container.innerHTML = ` +
+ +
+ +
+
`; + + const button = container.querySelector("#popup-lyrics-clickbutton"); + button.onclick = () => { + callback(); + }; + } else { + container = document.createElement("button"); + container.innerHTML = defaultValue; + container.className = "btn "; - function createButton(defaultValue, callback) { - const container = document.createElement("button"); - container.innerHTML = defaultValue; - container.className = "btn"; - - container.onclick = () => { - callback(); - }; + container.onclick = () => { + callback(); + }; + } return container; } + function createTextfield(name = null, defaultValue, placeholder, callback) { + let container; + + if (name) { + container = document.createElement("div"); + container.className = "setting-column"; + container.innerHTML = ` + + `; + + const textfield = container.querySelector("#popup-lyrics-textfield"); + textfield.onchange = () => { + callback(); + }; + } else { + container = document.createElement("input"); + container.placeholder = placeholder; + container.value = defaultValue; - function createTextfield(defaultValue, placeholder, callback) { - const container = document.createElement("input"); - container.placeholder = placeholder; - container.value = defaultValue; - - container.onchange = (e) => { - callback(e.target.value); - }; + container.onchange = (e) => { + callback(e.target.value); + }; + } return container; } + function descriptiveElement(element, description) { + element.innerHTML += `${description}`; + return element; + } + + function resetTokenButton(container) { + const button = container.querySelector("#popup-lyrics-refresh-token"); + if (button) { + button.innerHTML = "Refresh token"; + button.disabled = false; + } + } function musixmatchTokenElements(defaultVal, id) { - const button = createButton("Refresh token", clickRefresh); - button.style.marginTop = "10px"; - const textfield = createTextfield(defaultVal.token, `Place your ${id} token here`, changeTokenfield); - textfield.style.marginTop = "10px"; + const button = createButton(null, "Refresh token", clickRefresh); + button.className += "popup-config-col-margin"; + button.id = "popup-lyrics-refresh-token"; + const textfield = createTextfield(null, defaultVal.token, `Place your ${id} token here`, changeTokenfield); + textfield.className += "popup-config-col-margin"; function clickRefresh() { button.innerHTML = "Refreshing token..."; @@ -1154,12 +1234,10 @@ button.btn:disabled { button.innerHTML = "Failed to refresh token"; console.error("Failed to refresh token", response); } - button.disabled = false; }) .catch((error) => { button.innerHTML = "Failed to refresh token"; console.error("Failed to refresh token", error); - button.disabled = false; }); } From b8bbebafaa0b55adf2fdecf4ff869e20ab666555 Mon Sep 17 00:00:00 2001 From: Lseoksee Date: Mon, 24 Feb 2025 23:47:26 +0900 Subject: [PATCH 3/6] fix Clear Memory event --- Extensions/popupLyrics.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Extensions/popupLyrics.js b/Extensions/popupLyrics.js index 9ee5b3be5a..013c09d626 100644 --- a/Extensions/popupLyrics.js +++ b/Extensions/popupLyrics.js @@ -1197,7 +1197,9 @@ button.btn:disabled { return container; } function descriptiveElement(element, description) { - element.innerHTML += `${description}`; + const desc = document.createElement("span"); + desc.innerHTML = description; + element.append(desc); return element; } From a35b4041a3d0cf9e1cf715b08d99bed620e3300c Mon Sep 17 00:00:00 2001 From: Lseoksee Date: Tue, 25 Feb 2025 00:00:41 +0900 Subject: [PATCH 4/6] biome --- Extensions/popupLyrics.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/Extensions/popupLyrics.js b/Extensions/popupLyrics.js index 013c09d626..8a45fffd64 100644 --- a/Extensions/popupLyrics.js +++ b/Extensions/popupLyrics.js @@ -326,7 +326,7 @@ function PopupLyrics() { musixmatch: { on: boolLocalStorage("popup-lyrics:services:musixmatch:on"), call: LyricProviders.fetchMusixmatch, - desc: `Fully compatible with Spotify. Requires a token that can be retrieved from the official Musixmatch app. If you have problems with retrieving lyrics, try refreshing the token by clicking Refresh Token button.`, + desc: "Fully compatible with Spotify. Requires a token that can be retrieved from the official Musixmatch app. If you have problems with retrieving lyrics, try refreshing the token by clicking Refresh Token button.", token: LocalStorage.get("popup-lyrics:services:musixmatch:token") || "2005218b74f939209bda92cb633c7380612e14cb7fe92dcd6a780f", }, spotify: { @@ -882,7 +882,7 @@ button.switch.small { padding: 6px; } button.btn { - font-weight: 700; + font-weight: 700; display: block; background-color: rgba(var(--spice-rgb-shadow), .7); border-radius: 500px; @@ -896,7 +896,7 @@ button.btn { } button.btn:hover { transform: scale(1.04); - border-color: var(--spice-text); + border-color: var(--spice-text); } button.btn:disabled { opacity: 0.5; @@ -1139,7 +1139,8 @@ button.btn:disabled { return container; } - function createButton(name = null, defaultValue, callback) { + // if name is null, the element can be used without a description. + function createButton(name, defaultValue, callback) { let container; if (name) { @@ -1168,7 +1169,8 @@ button.btn:disabled { return container; } - function createTextfield(name = null, defaultValue, placeholder, callback) { + // if name is null, the element can be used without a description. + function createTextfield(name, defaultValue, placeholder, callback) { let container; if (name) { @@ -1245,7 +1247,7 @@ button.btn:disabled { function changeTokenfield(value) { userConfigs.services.musixmatch.token = value; - LocalStorage.set(`popup-lyrics:services:musixmatch:token`, value); + LocalStorage.set("popup-lyrics:services:musixmatch:token", value); updateTrack(true); } From e42458f38cb9988036c2e1c887826208918b0f34 Mon Sep 17 00:00:00 2001 From: Jeong Hyeon Date: Tue, 25 Feb 2025 00:04:51 +0900 Subject: [PATCH 5/6] css align --- Extensions/popupLyrics.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Extensions/popupLyrics.js b/Extensions/popupLyrics.js index 8a45fffd64..fc79a98c7f 100644 --- a/Extensions/popupLyrics.js +++ b/Extensions/popupLyrics.js @@ -837,7 +837,7 @@ function PopupLyrics() { .setting-row { display: flex; justify-content: space-between; - align-items: center; + align-items: center; } .setting-row::after { content: ""; @@ -852,14 +852,14 @@ function PopupLyrics() { .setting-row .col.description { padding-right: 15px; cursor: default; - width: 50%; + width: 50%; } .setting-row .col.action { justify-content: flex-end; - width: 50%; + width: 50%; } .popup-config-col-margin { - margin-top: 10px; + margin-top: 10px; } button.switch { align-items: center; @@ -882,9 +882,9 @@ button.switch.small { padding: 6px; } button.btn { - font-weight: 700; - display: block; - background-color: rgba(var(--spice-rgb-shadow), .7); + font-weight: 700; + display: block; + background-color: rgba(var(--spice-rgb-shadow), .7); border-radius: 500px; transition-duration: 33ms; transition-property: background-color, border-color, color, box-shadow, filter, transform; @@ -895,8 +895,8 @@ button.btn { cursor: pointer; } button.btn:hover { - transform: scale(1.04); - border-color: var(--spice-text); +transform: scale(1.04); + border-color: var(--spice-text); } button.btn:disabled { opacity: 0.5; From b36854cf1ae38a9b59c7e1548a78161652579fd3 Mon Sep 17 00:00:00 2001 From: Jeong Hyeon Date: Wed, 26 Feb 2025 22:29:53 +0900 Subject: [PATCH 6/6] css align --- Extensions/popupLyrics.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Extensions/popupLyrics.js b/Extensions/popupLyrics.js index fc79a98c7f..74f42b552a 100644 --- a/Extensions/popupLyrics.js +++ b/Extensions/popupLyrics.js @@ -895,7 +895,7 @@ button.btn { cursor: pointer; } button.btn:hover { -transform: scale(1.04); + transform: scale(1.04); border-color: var(--spice-text); } button.btn:disabled {