您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Greasy Fork is available in English.
Download music from Spotify, Qobuz, Tidal, Soundcloud, Deezer, Amazon Music and Yandex Music via Lucida. Adds download buttons and floating button.
当前为
// ==UserScript== // @name Lucida Downloader // @description Download music from Spotify, Qobuz, Tidal, Soundcloud, Deezer, Amazon Music and Yandex Music via Lucida. Adds download buttons and floating button. // @icon https://raw.githubusercontent.com/afkarxyz/misc-scripts/refs/heads/main/lucida/lucida.png // @version 1.4 // @author afkarxyz // @namespace https://github.com/afkarxyz/misc-scripts/ // @supportURL https://github.com/afkarxyz/misc-scripts/issues // @license MIT // @match https://open.spotify.com/* // @match https://listen.tidal.com/* // @match https://music.yandex.com/* // @match https://music.amazon.com/* // @match https://www.deezer.com/* // @match https://soundcloud.com/* // @match https://www.qobuz.com/* // @grant GM_setValue // @grant GM_getValue // @grant GM_registerMenuCommand // @grant GM_addStyle // ==/UserScript== (function() { 'use strict'; const DOMAINS = ['lucida.to', 'lucida.su']; const BASE_URL = 'https://raw.githubusercontent.com/afkarxyz/misc-scripts/refs/heads/main/lucida/'; const LOGO_SVG = `<svg xml:space="preserve" width="48" height="28" viewBox="0 0 213.86 126.117" xmlns="http://www.w3.org/2000/svg"><g style="display:inline" transform="translate(-92.77 -153.171)"><ellipse class="st17" cx="199.7" cy="211.95" rx="103.93" ry="51" style="fill:#f42e8d;stroke:#fff;stroke-width:6;stroke-miterlimit:10"/><ellipse class="st18" cx="199.97" cy="211.95" rx="93.24" ry="41" style="fill:#f42e8d;stroke:#fff;stroke-width:3;stroke-miterlimit:10"/></g><path class="st19" style="fill:#fff;stroke:#fff;stroke-width:6;stroke-miterlimit:10" transform="translate(-92.77 -153.171)" d="M216.68 222.27v-8.79l2.1-2.21 5.4.25v10.75zM248.83 222.27v-8.79l2.1-2.21 5.4.25v10.75z"/><path class="st19" style="fill:#fff;stroke:#fff;stroke-width:6;stroke-miterlimit:10" transform="translate(-92.77 -153.171)" d="M216.68 223.56v-8.79l2.1-2.21 5.4.25v10.75zM125.12 237.48v-54.5l3.78-4.75h9.47v59.25zM139.86 204.18l3.75-3.28h7.36l-.18 16.25h9.01l.28-16.25h9.53l.25 36.58h-30zM171.18 204.4l3.65-3.5h23.6v12.19l-15.25.21v10.47l15.25-.05v13.76h-27.25zM199.97 200.9h12.5v36.59h-12.5z"/><path class="st19" style="fill:#fff;stroke:#fff;stroke-width:6;stroke-miterlimit:10" d="m214.09 204.41 4-3.51h14.44l-.25-17.14 3-5.53h9.92l.14 22.67v36.58h-31.25zM251.42 200.9h19.99l.25 4.25 3.49-4.25h6.35v36.58H247l-.25-32.08zM116.91 237.48v-54.5l3.78-4.75h9.47v59.25zM131.64 204.18l3.75-3.28h7.36l-.18 16.25h9.01l.29-16.25h9.52l.25 36.58h-30zM162.97 204.4l3.64-3.5h23.61v12.19l-15.25.21v10.47l15.25-.05v13.76h-27.25zM191.76 200.9h12.5v36.59h-12.5z" transform="translate(-92.77 -153.171)"/><path class="st19" style="fill:#fff;stroke:#fff;stroke-width:6;stroke-miterlimit:10" d="m205.88 204.41 4-3.51h14.43l-.25-17.14 3-5.53h9.93l.14 22.67v36.58h-31.25zM243.21 200.9h19.99l.24 4.25 3.5-4.25h6.34v36.58h-34.5l-.25-32.08zM162.97 204.4l3.64-3.5h23.61v12.19l-15.25.21v10.47l15.25-.05v13.76h-27.25zM131.64 204.18l3.75-3.28h7.36l-.18 16.25h9.01l.29-16.25h9.52l.25 36.58h-30z" transform="translate(-92.77 -153.171)"/><g transform="translate(-92.77 -153.171)"><path class="st19" style="fill:#fff;stroke:#fff;stroke-width:6;stroke-miterlimit:10" d="M216.68 222.27v-8.79l2.1-2.21 5.4.25v10.75zM248.83 222.27v-8.79l2.1-2.21 5.4.25v10.75z"/><path class="st19" style="fill:#fff;stroke:#fff;stroke-width:6;stroke-miterlimit:10" d="M216.68 223.56v-8.79l2.1-2.21 5.4.25v10.75z"/><circle class="st21" cx="279.8" cy="173.88" r="4.56" style="stroke:#fff;stroke-width:6;stroke-miterlimit:10"/><path class="st21" style="stroke:#fff;stroke-width:6;stroke-miterlimit:10" d="m132.88 255.72 2.83 4.71 5.1-1.26-3.34 4.17 2.83 4.71-4.9-2.13-3.35 4.17.32-5.49-4.91-2.14 5.1-1.25z"/><circle class="st21" cx="199.97" cy="236.84" r="5.62" style="stroke:#fff;stroke-width:6;stroke-miterlimit:10"/><ellipse class="st21" cx="184.25" cy="245.48" rx="3.38" ry="3.14" style="stroke:#fff;stroke-width:6;stroke-miterlimit:10"/><path class="st21" style="stroke:#fff;stroke-width:6;stroke-miterlimit:10" d="M216.68 223.77v-8.79l2.1-2.21 5.4.25v10.75zM248.83 223.77v-8.79l2.1-2.21 5.4.25v10.75zM194.48 175.23l4.06 3.92 4.72-2.6-2.21 5.03 4.06 3.92-5.43-.82-2.22 5.02-1.14-5.52-5.43-.82 4.73-2.6z"/></g><path class="st22" d="M21.66 110.899c-6.48-10.44 27.24-43.11 75.33-72.98 48.09-29.87 86.75-41.62 93.23-31.18 6.48 10.44-21.67 39.11-69.76 68.98-48.09 29.87-92.32 45.62-98.8 35.18z" style="fill:none;stroke:#fff;stroke-width:6;stroke-miterlimit:10"/><path class="st7" style="stroke:#000;stroke-width:3;stroke-miterlimit:10" d="M125.12 237.48v-54.5l3.78-4.75h9.47v59.25zM139.86 204.18l3.75-3.28h7.36l-.18 16.25h9.01l.28-16.25h9.53l.25 36.58h-30zM171.18 204.4l3.65-3.5h23.6v12.19l-15.25.21v10.47l15.25-.05v13.76h-27.25zM199.97 200.9h12.5v36.59h-12.5zM214.09 204.41l4-3.51h14.44l-.25-17.14 3-5.53h9.92l.14 22.67v36.58h-31.25zM251.42 200.9h19.99l.25 4.25 3.49-4.25h6.35v36.58H247l-.25-32.08z" transform="translate(-92.77 -153.171)"/><path class="st23" style="fill:#fff;stroke:#000;stroke-width:3;stroke-miterlimit:10" transform="translate(-92.77 -153.171)" d="m131.64 204.18 3.75-3.28h7.36l-.18 16.25h9.01l.29-16.25h9.52l.25 36.58h-30zM162.97 204.4l3.64-3.5h23.61v12.19l-15.25.21v10.47l15.25-.05v13.76h-27.25z"/><path class="st23" style="fill:#fff;stroke:#000;stroke-width:3;stroke-miterlimit:10" d="M98.99 47.729h12.5v36.59h-12.5z"/><path class="st23" style="fill:#fff;stroke:#000;stroke-width:3;stroke-miterlimit:10" transform="translate(-92.77 -153.171)" d="m205.88 204.41 4-3.51h14.43l-.25-17.14 3-5.53h9.93l.14 22.67v36.58h-31.25zM243.21 200.9h19.99l.24 4.25 3.5-4.25h6.34v36.58h-34.5l-.25-32.08z"/><circle class="st7" cx="190.62" cy="7.149" r="2.38" style="stroke:#000;stroke-width:3;stroke-miterlimit:10"/><circle class="st7" cx="187.03" cy="20.709" r="4.56" style="stroke:#000;stroke-width:3;stroke-miterlimit:10"/><path class="st7" style="stroke:#000;stroke-width:3;stroke-miterlimit:10" transform="translate(-92.77 -153.171)" d="m132.88 255.72 2.83 4.71 5.1-1.26-3.34 4.17 2.83 4.71-4.9-2.13-3.35 4.17.32-5.49-4.91-2.14 5.1-1.25z"/><circle class="st7" cx="107.2" cy="83.669" r="5.62" style="stroke:#000;stroke-width:3;stroke-miterlimit:10"/><ellipse class="st7" cx="91.48" cy="92.309" rx="3.38" ry="3.14" style="stroke:#000;stroke-width:3;stroke-miterlimit:10"/><path class="st7" style="stroke:#000;stroke-width:3;stroke-miterlimit:10" transform="translate(-92.77 -153.171)" d="m194.48 175.23 4.06 3.92 4.72-2.6-2.21 5.03 4.06 3.92-5.43-.82-2.22 5.02-1.14-5.52-5.43-.82 4.73-2.6zM248.83 223.77v-8.79l2.1-2.21 5.4.25v10.75zM216.68 223.77v-8.79l2.1-2.21 5.4.25v10.75z"/><path class="st9" d="M21.66 110.899c-6.48-10.44 27.24-43.11 75.33-72.98 48.09-29.87 86.75-41.62 93.23-31.18 6.48 10.44-21.67 39.11-69.76 68.98-48.09 29.87-92.32 45.62-98.8 35.18z" style="fill:none;stroke:#000;stroke-width:3;stroke-miterlimit:10"/><path class="st23" style="fill:#fff;stroke:#000;stroke-width:3;stroke-miterlimit:10" transform="translate(-92.77 -153.171)" d="m162.97 204.4 3.64-3.5h23.61v12.19l-15.25.21v10.47l15.25-.05v13.76h-27.25zM131.64 204.18l3.75-3.28h7.36l-.18 16.25h9.01l.29-16.25h9.52l.25 36.58h-30zM116.91 237.48v-54.5l3.78-4.75h9.47v59.25z"/></svg>`; const SERVICES = { '': { name: 'Disabled', icon: '' }, 'spotify': { name: 'Spotify', icon: `${BASE_URL}spotify.png` }, 'qobuz': { name: 'Qobuz', icon: `${BASE_URL}qobuz.png` }, 'tidal': { name: 'Tidal', icon: `${BASE_URL}tidal.svg` }, 'soundcloud': { name: 'Soundcloud', icon: `${BASE_URL}soundcloud.ico` }, 'deezer': { name: 'Deezer', icon: `${BASE_URL}deezer.ico` }, 'amazon': { name: 'Amazon Music', icon: `${BASE_URL}amazon.png` } }; GM_addStyle(` .lucida-modal *, .lucida-modal *::before, .lucida-modal *::after { all: initial; box-sizing: border-box; margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif !important; font-weight: normal !important; font-size: 14px !important; color: #333; } .lucida-modal-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.7); display: flex; justify-content: center; align-items: center; z-index: 10000; font-weight: normal; } .lucida-modal { background: #fff; padding: 20px; border-radius: 8px; width: 400px; max-width: 90%; color: #333; font-size: 14px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); font-weight: normal !important; } .lucida-modal h2 { margin: 0 0 20px; color: #f42e8d; font-size: 18px !important; font-weight: 600 !important; line-height: 1.4; } .lucida-modal .preference-group { margin-bottom: 20px; color: #333; } .lucida-modal label { display: block; margin-top: 20px; margin-bottom: 8px; font-weight: 600 !important; font-size: 14px !important; color: #333; } .lucida-modal .header { display: flex; align-items: center; justify-content: flex-start; } .lucida-modal .header img { width: 64px; height: 64px; object-fit: contain; } .lucida-modal .header h2 { margin: 0; } .lucida-modal .preference-group label:first-child { margin-top: 0; } .lucida-modal select { -webkit-appearance: none; -moz-appearance: none; appearance: none; width: 100%; padding: 8px 32px 8px 12px; border: 1px solid #ddd; border-radius: 4px; background: #fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23666' d='M6 8L1 3h10z'/%3E%3C/svg%3E") calc(100% - 12px) center no-repeat; cursor: pointer; font-size: 14px !important; color: #333; } .lucida-modal select:hover { border-color: #f42e8d; } .lucida-modal select:focus { outline: none; border-color: #f42e8d; box-shadow: 0 0 0 2px rgba(244, 46, 141, 0.2); } .custom-options { scrollbar-width: thin; scrollbar-color: #f42e8d #f0f0f0; font-size: 14px !important; } .custom-options::-webkit-scrollbar { width: 8px; } .custom-options::-webkit-scrollbar-track { background: #f0f0f0; border-radius: 4px; } .custom-options::-webkit-scrollbar-thumb { background: #f42e8d; border-radius: 4px; } .custom-options::-webkit-scrollbar-thumb:hover { background: #d41d7a; } .service-select-wrapper { position: relative; margin-bottom: 15px; } .custom-select { width: 100%; padding: 8px 32px 8px 12px; border: 1px solid #ddd; border-radius: 4px; background: #fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23666' d='M6 8L1 3h10z'/%3E%3C/svg%3E") calc(100% - 12px) center no-repeat; display: flex; align-items: center; gap: 8px; cursor: pointer; transition: all 0.2s ease; user-select: none; font-size: 14px !important; color: #333; } .custom-select span { font-size: 14px !important; color: #333; } .custom-select:hover { border-color: #f42e8d; } .custom-options { position: absolute; top: 100%; left: 0; right: 0; background: white; border: 1px solid #ddd; border-radius: 4px; margin-top: 4px; max-height: 200px; overflow-y: auto; z-index: 1000; display: none; box-shadow: 0 2px 8px rgba(0,0,0,0.1); } .custom-options.show { display: block; } .service-option { display: flex; align-items: center; gap: 8px; padding: 8px 12px; cursor: pointer; transition: background-color 0.2s ease; font-weight: normal !important; font-size: 14px !important; color: #333; } .service-option span { font-size: 14px !important; color: #333; } .service-option:hover { background-color: #f5f5f5; } .service-option img, .custom-select img { width: 16px; height: 16px; object-fit: contain; } .lucida-modal .buttons { display: flex; justify-content: flex-end; gap: 10px; margin-top: 20px; } .lucida-modal button { padding: 8px 16px; border: none; border-radius: 4px; cursor: pointer; font-weight: 500; transition: all 0.2s ease; font-size: 14px !important; } .lucida-modal .save-btn { background: linear-gradient(135deg, #f42e8d, #b91c68); color: white; } .lucida-modal .save-btn:hover { background: linear-gradient(135deg, #ff3d9c, #d02077); transform: translateY(-1px); box-shadow: 0 2px 8px rgba(244, 46, 141, 0.4); } .lucida-modal .save-btn:active { transform: translateY(0); box-shadow: 0 1px 4px rgba(244, 46, 141, 0.4); } .lucida-modal .cancel-btn { background: #eee; color: #333; } .lucida-modal .cancel-btn:hover { background: #ddd; color: #333; transform: translateY(-1px); } .lucida-modal .cancel-btn:active { transform: translateY(0); } .floating-button { position: fixed; width: 80px; height: 80px; background-color: transparent; border-radius: 50%; display: flex; justify-content: center; align-items: center; cursor: move; z-index: 9999; opacity: 0.3; transition: opacity 0.3s ease; border: none; } .floating-button:hover { opacity: 1; } .floating-button svg { width: 48px; height: auto; cursor: pointer; } [role='grid'] { margin-left: 50px; } [data-testid="tracklist-row"] { position: relative; } [role="presentation"] > * { contain: unset; } .btn { width: 40px; height: 40px; border-radius: 50%; border: 0; position: relative; cursor: pointer; transition: all 0.2s ease; box-shadow: 0 2px 5px rgba(0,0,0,0.2); display: flex; align-items: center; justify-content: center; background: linear-gradient(135deg, #f42e8d, #b91c68); } .btn:hover { transform: scale(1.1); box-shadow: 0 4px 8px rgba(0,0,0,0.3); } .btn .icon { width: 50%; height: 50%; background-position: center; background-repeat: no-repeat; background-size: contain; background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path fill="%23ffffff" d="M222.2 319.2c.5 .5 1.1 .8 1.8 .8s1.4-.3 1.8-.8L350.2 187.3c1.2-1.2 1.8-2.9 1.8-4.6c0-3.7-3-6.7-6.7-6.7L288 176c-8.8 0-16-7.2-16-16l0-120c0-4.4-3.6-8-8-8l-80 0c-4.4 0-8 3.6-8 8l0 120c0 8.8-7.2 16-16 16l-57.3 0c-3.7 0-6.7 3-6.7 6.7c0 1.7 .7 3.3 1.8 4.6L222.2 319.2zM224 352c-9.5 0-18.6-3.9-25.1-10.8L74.5 209.2C67.8 202 64 192.5 64 182.7c0-21.4 17.3-38.7 38.7-38.7l41.3 0 0-104c0-22.1 17.9-40 40-40l80 0c22.1 0 40 17.9 40 40l0 104 41.3 0c21.4 0 38.7 17.3 38.7 38.7c0 9.9-3.8 19.3-10.5 26.5L249.1 341.2c-6.5 6.9-15.6 10.8-25.1 10.8zM32 336l0 96c0 26.5 21.5 48 48 48l288 0c26.5 0 48-21.5 48-48l0-96c0-8.8 7.2-16 16-16s16 7.2 16 16l0 96c0 44.2-35.8 80-80 80L80 512c-44.2 0-80-35.8-80-80l0-96c0-8.8 7.2-16 16-16s16 7.2 16 16z"/></svg>'); } [data-testid="tracklist-row"] .btn { position: absolute; top: 50%; right: 100%; margin-top: -20px; margin-right: 10px; } .N7GZp8IuWPJvCPz_7dOg .btn { width: 24px; height: 24px; transform-origin: center; position: absolute; top: 50%; right: 100%; margin-top: -12px !important; margin-right: 10px; } .N7GZp8IuWPJvCPz_7dOg .btn .icon { transform: scale(0.85); width: 65%; height: 65%; } `); function createServiceOption(value, service) { const option = document.createElement('div'); option.className = 'service-option'; option.dataset.value = value; if (service.icon) { const img = document.createElement('img'); img.src = service.icon; img.alt = service.name; img.style.display = 'none'; img.onload = () => { img.style.display = 'inline'; }; option.appendChild(img); } const span = document.createElement('span'); span.textContent = service.name; option.appendChild(span); return option; } function updateCustomSelect(customSelect, value) { const service = SERVICES[value]; let content = `<span>${service.name}</span>`; if (service.icon) { const img = new Image(); img.src = service.icon; img.style.display = 'none'; img.onload = () => { img.style.display = 'inline'; customSelect.querySelector('img')?.style.setProperty('display', 'inline'); }; content = `<img src="${service.icon}" alt="${service.name}" style="display: none;"><span>${service.name}</span>`; } customSelect.innerHTML = content; } function createPreferencesModal() { const existingModal = document.querySelector('.lucida-modal-overlay'); if (existingModal) { existingModal.remove(); } const modalHTML = ` <div class="lucida-modal-overlay"> <div class="lucida-modal"> <div class="header"> <h2>Lucida Preferences</h2> <img src="${BASE_URL}lucida.svg" alt="Lucida Icon" class="lucida-icon" style="cursor: pointer; margin-left: auto; display: none;"> </div> <div class="preference-group"> <label for="domain-select">Domain</label> <select id="domain-select"> <option value="random">Random</option> <option value="lucida.to">Lucida.to</option> <option value="lucida.su">Lucida.su</option> </select> <label for="service-select">Service Resolver</label> <div class="service-select-wrapper"> <div class="custom-select" id="custom-service-select"> <img src="" alt="" style="display: none;"> <span>Select a service</span> </div> <div class="custom-options"> </div> </div> <input type="hidden" id="service-select"> <label for="float-select">Float Icon</label> <select id="float-select"> <option value="enabled">Enabled</option> <option value="disabled">Disabled</option> </select> </div> <div class="buttons"> <button class="cancel-btn">Cancel</button> <button class="save-btn">Save</button> </div> </div> </div> `; const modalContainer = document.createElement('div'); modalContainer.innerHTML = modalHTML; document.body.appendChild(modalContainer.firstElementChild); const customSelect = document.getElementById('custom-service-select'); const customOptions = document.querySelector('.custom-options'); const serviceSelect = document.getElementById('service-select'); const domainSelect = document.getElementById('domain-select'); const floatSelect = document.getElementById('float-select'); const lucidaIcon = document.querySelector('.lucida-icon'); lucidaIcon.onload = () => { lucidaIcon.style.display = 'inline'; }; lucidaIcon.onerror = () => { lucidaIcon.style.display = 'none'; }; lucidaIcon.addEventListener('click', () => { const domainPref = GM_getValue('domainPreference', 'random'); let domain = domainPref === 'random' ? DOMAINS[Math.floor(Math.random() * DOMAINS.length)] : domainPref; window.open(`https://${domain}`, '_blank'); }); if (domainSelect) domainSelect.value = GM_getValue('domainPreference', 'random'); if (floatSelect) floatSelect.value = GM_getValue('floatIconEnabled', 'enabled'); const savedService = GM_getValue('targetService', ''); if (savedService && SERVICES[savedService]) { updateCustomSelect(customSelect, savedService); serviceSelect.value = savedService; } Object.entries(SERVICES).forEach(([value, service]) => { const option = createServiceOption(value, service); customOptions.appendChild(option); option.addEventListener('click', () => { serviceSelect.value = value; updateCustomSelect(customSelect, value); customOptions.classList.remove('show'); }); }); customSelect.addEventListener('click', () => { customOptions.classList.toggle('show'); }); document.addEventListener('click', (e) => { if (!e.target.closest('.service-select-wrapper')) { customOptions.classList.remove('show'); } }); const saveBtn = document.querySelector('.save-btn'); if (saveBtn) { saveBtn.addEventListener('click', () => { if (domainSelect && serviceSelect && floatSelect) { GM_setValue('domainPreference', domainSelect.value); GM_setValue('targetService', serviceSelect.value); GM_setValue('floatIconEnabled', floatSelect.value); const floatingButton = document.querySelector('.floating-button'); if (floatingButton) { floatingButton.style.display = floatSelect.value === 'enabled' ? 'flex' : 'none'; } } document.querySelector('.lucida-modal-overlay').remove(); }); } const cancelBtn = document.querySelector('.cancel-btn'); if (cancelBtn) { cancelBtn.addEventListener('click', () => { document.querySelector('.lucida-modal-overlay').remove(); }); } const modalOverlay = document.querySelector('.lucida-modal-overlay'); if (modalOverlay) { modalOverlay.addEventListener('click', (e) => { if (e.target === modalOverlay) { modalOverlay.remove(); } }); } } function setupMenuCommand() { try { GM_registerMenuCommand('Lucida Preferences', () => { console.log('Opening preferences modal...'); createPreferencesModal(); }); } catch (error) { console.error('Error registering menu command:', error); } } function openInLucida(trackUrl) { const currentUrl = encodeURIComponent(trackUrl || window.location.href); const prefs = getPreferences(); let domain = prefs.domainPreference === 'random' ? DOMAINS[Math.floor(Math.random() * DOMAINS.length)] : prefs.domainPreference; let url = `https://${domain}/?url=${currentUrl}&country=auto`; if (prefs.targetService) { url += `&to=${prefs.targetService}`; } window.open(url, '_blank'); } const getPreferences = () => ({ targetService: GM_getValue('targetService', ''), domainPreference: GM_getValue('domainPreference', 'random') }); function addButton(el) { const button = document.createElement('button'); button.className = 'btn'; const icon = document.createElement('div'); icon.className = 'icon'; button.appendChild(icon); el.appendChild(button); return button; } function addNowPlayingButton() { const downloadButton = document.createElement('button'); downloadButton.className = 'Lucida-Button-sc-1dqy6lx-0 dmdXQN'; downloadButton.innerHTML = '<span aria-hidden="true" class="IconWrapper__Wrapper-sc-16usrgb-0 hYdsxw"><svg data-encore-id="icon" role="img" aria-hidden="true" viewBox="0 0 448 512" class="Svg-sc-ytk21e-0 dYnaPI" width="24" height="24" fill="currentColor"><path d="M114.2 192L224 302 333.8 192 280 192c-13.3 0-24-10.7-24-24l0-120-64 0 0 120c0 13.3-10.7 24-24 24l-53.8 0zM224 352c-11.5 0-22.5-4.6-30.6-12.7L77.6 223.2C68.9 214.5 64 202.7 64 190.4c0-25.6 20.8-46.4 46.4-46.4l33.6 0 0-96c0-26.5 21.5-48 48-48l64 0c26.5 0 48 21.5 48 48l0 96 33.6 0c25.6 0 46.4 20.8 46.4 46.4c0 12.3-4.9 24.1-13.6 32.8L254.6 339.3c-8.1 8.1-19.1 12.7-30.6 12.7zM48 344l0 80c0 22.1 17.9 40 40 40l272 0c22.1 0 40-17.9 40-40l0-80c0-13.3 10.7-24 24-24s24 10.7 24 24l0 80c0 48.6-39.4 88-88 88L88 512c-48.6 0-88-39.4-88-88l0-80c0-13.3 10.7-24 24-24s24 10.7 24 24z"/></svg></span>'; downloadButton.style.cssText = 'background:transparent;border:none;color:#f42e8d;cursor:pointer;padding:8px;margin:0 4px;transition:transform .2s ease'; downloadButton.onmouseover = () => downloadButton.style.transform = 'scale(1.1)'; downloadButton.onmouseout = () => downloadButton.style.transform = 'scale(1)'; downloadButton.onclick = () => { const link = document.querySelector('a[href*="spotify:track:"]'); if (link) { const match = link.getAttribute('href').match(/spotify:track:([a-zA-Z0-9]+)/); if (match) { const trackUrl = `https://open.spotify.com/track/${match[1]}`; openInLucida(trackUrl); } } }; const container = document.querySelector('.snFK6_ei0caqvFI6As9Q')?.querySelector('.deomraqfhIAoSB3SgXpu'); if (container && !container.querySelector('.Lucida-Button-sc-1dqy6lx-0')) { container.appendChild(downloadButton); } } function animate() { const currentUrl = window.location.href; const urlParts = currentUrl.split('/'); const type = urlParts[3]; addNowPlayingButton(); if (type === 'track') { const actionBarRow = document.querySelector('.eSg4ntPU2KQLfpLGXAww[data-testid="action-bar-row"]'); if (actionBarRow && !actionBarRow.hasButtons) { const downloadButton = addButton(actionBarRow); downloadButton.onclick = function() { const spotifyId = urlParts[4].split('?')[0]; openInLucida(`https://open.spotify.com/track/${spotifyId}`); } actionBarRow.hasButtons = true; } } if (type === 'artist') { const tracks = document.querySelectorAll('[role="gridcell"]'); tracks.forEach(track => { if (!track.hasButtons) { const downloadButton = addButton(track); downloadButton.onclick = function() { const btn = track.querySelector('[data-testid="more-button"]'); if (btn) { btn.click(); setTimeout(() => { const highlightEl = document.querySelector('#context-menu a[href*="highlight"]'); if (highlightEl) { const highlight = highlightEl.href.match(/highlight=(.+)/)[1]; document.dispatchEvent(new MouseEvent('mousedown')); const spotifyId = highlight.split(':')[2]; openInLucida(`https://open.spotify.com/track/${spotifyId}`); } }, 1); } } track.hasButtons = true; } }); } if (type === 'album' || type === 'playlist' || type === 'track') { const tracks = document.querySelectorAll('[data-testid="tracklist-row"]'); tracks.forEach(track => { if (!track.hasButtons) { const downloadButton = addButton(track); downloadButton.onclick = function() { const trackLink = track.querySelector('a[href^="/track"]'); if (trackLink) { openInLucida(trackLink.href); } else { const btn = track.querySelector('[data-testid="more-button"]'); if (btn) { btn.click(); setTimeout(() => { const highlightEl = document.querySelector('#context-menu a[href*="highlight"]'); if (highlightEl) { const highlight = highlightEl.href.match(/highlight=(.+)/)[1]; document.dispatchEvent(new MouseEvent('mousedown')); const spotifyId = highlight.split(':')[2]; openInLucida(`https://open.spotify.com/track/${spotifyId}`); } }, 1); } } } track.hasButtons = true; } }); } } function animateLoop() { if (window.location.hostname === 'open.spotify.com') { animate(); } requestAnimationFrame(animateLoop); } const button = document.createElement('button'); button.className = 'floating-button'; button.innerHTML = LOGO_SVG; const savedPosition = { left: GM_getValue('buttonLeft', '20'), top: GM_getValue('buttonTop', '20') }; button.style.left = savedPosition.left + 'px'; button.style.top = savedPosition.top + 'px'; let isDragging = false; let startX, startY; button.addEventListener('mousedown', e => { if (e.target.tagName.toLowerCase() !== 'svg') { isDragging = true; startX = e.clientX - button.offsetLeft; startY = e.clientY - button.offsetTop; } }); document.addEventListener('mousemove', e => { if (!isDragging) return; let left = e.clientX - startX; let top = e.clientY - startY; left = Math.max(0, Math.min(window.innerWidth - button.offsetWidth, left)); top = Math.max(0, Math.min(window.innerHeight - button.offsetHeight, top)); button.style.left = left + 'px'; button.style.top = top + 'px'; }); document.addEventListener('mouseup', () => { if (!isDragging) return; isDragging = false; const SNAP = 20; const rect = button.getBoundingClientRect(); if (rect.left < SNAP) button.style.left = '0px'; if (rect.top < SNAP) button.style.top = '0px'; if (window.innerWidth - rect.right < SNAP) button.style.left = (window.innerWidth - rect.width) + 'px'; if (window.innerHeight - rect.bottom < SNAP) button.style.top = (window.innerHeight - rect.height) + 'px'; GM_setValue('buttonLeft', button.style.left.replace('px', '')); GM_setValue('buttonTop', button.style.top.replace('px', '')); }); button.addEventListener('click', e => { if (e.target.closest('svg')) { openInLucida(); } }); if (GM_getValue('floatIconEnabled', 'enabled') === 'disabled') { button.style.display = 'none'; } document.body.appendChild(button); setupMenuCommand(); requestAnimationFrame(animateLoop); })();