Greasy Fork

Lucida Downloader

Download music from Spotify, Qobuz, Tidal, SoundCloud & Amazon Music via Lucida.

目前为 2025-04-24 提交的版本。查看 最新版本

// ==UserScript==
// @name         Lucida Downloader
// @description  Download music from Spotify, Qobuz, Tidal, SoundCloud & Amazon Music via Lucida.
// @icon         https://raw.githubusercontent.com/afkarxyz/userscripts/refs/heads/main/assets/lucida/lucida.png
// @version      2.5
// @author       afkarxyz
// @namespace    https://github.com/afkarxyz/userscripts/
// @supportURL   https://github.com/afkarxyz/userscripts/issues
// @license      MIT
// @match        https://open.spotify.com/*
// @match        https://listen.tidal.com/*
// @match        https://music.amazon.com/*
// @match        https://soundcloud.com/*
// @match        https://www.qobuz.com/*
// @match        https://lucida.to/*
// @match        https://lucida.su/*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_addStyle
// @grant        GM_xmlhttpRequest
// ==/UserScript==

(function() {
    'use strict';

    const DOMAINS = ['lucida.to', 'lucida.su'];

    const BASE_URL = 'https://raw.githubusercontent.com/afkarxyz/userscripts/refs/heads/main/assets/lucida/';
    
    const SERVICES = {
        'tidal': {
            name: 'Tidal',
            icon: `${BASE_URL}tidal.png`
        },
        'soundcloud': {
            name: 'Soundcloud',
            icon: `${BASE_URL}soundcloud.png`
        },
        'deezer': {
            name: 'Deezer',
            icon: `${BASE_URL}deezer.png`
        },
        'amazon': {
            name: 'Amazon Music',
            icon: `${BASE_URL}amazon.png`
        }
    };

    const SERVICE_STATUS = {
        tidal: false,
        soundcloud: false,
        deezer: false,
        amazon: false
    };

    const fontLink = document.createElement('link');
    fontLink.rel = 'stylesheet';
    fontLink.href = 'https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,300..800;1,300..800&display=swap';
    document.head.appendChild(fontLink);

    GM_addStyle(`
        .floating-settings-button {
            position: fixed;
            top: 10%;
            right: 0;
            width: 2.5rem;
            height: 2.5rem;
            border-radius: 1.25rem 0 0 1.25rem;
            background: rgba(60, 60, 60, 0.9);
            border: none;
            cursor: pointer;
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            z-index: 9999;
            transition: all 0.3s ease;
            font-family: 'Open Sans', sans-serif;
        }
        
        .floating-settings-button .lucida-icon {
            width: 1.75rem;
            height: 1.75rem;
            object-fit: contain;
            transition: all 0.3s ease;
        }
        
        .floating-settings-button:hover .button-menu {
            opacity: 1;
            visibility: visible;
        }
        
        .button-menu {
            position: absolute;
            right: 0;
            top: calc(100% + 1rem);
            display: flex;
            flex-direction: column;
            background: rgba(60, 60, 60, 0.9);
            border-radius: 0.75rem 0 0 0.75rem;
            padding: 0.5rem 0;
            opacity: 0;
            visibility: hidden;
            transition: all 0.3s ease;
        }
        
        .menu-item {
            width: 2.5rem;
            height: 2.5rem;
            display: flex;
            align-items: center;
            justify-content: center;
            cursor: pointer;
            transition: all 0.2s ease;
            color: white;
            font-family: 'Open Sans', sans-serif;
            font-size: 0.75rem;
            font-weight: 500;
        }

        .menu-item:hover {
            background: rgba(255, 255, 255, 0.1);
            color: #f42e8d;
        }

        .menu-item:hover svg {
            fill: #f42e8d;
        }
        
        .menu-item svg {
            width: 1.25rem;
            height: 1.25rem;
            fill: white;
        }
        
        .domain-option {
            width: 2.5rem;
            height: 2rem;
            display: flex;
            align-items: center;
            justify-content: center;
            cursor: pointer;
            transition: all 0.2s ease;
            color: white;
            font-family: 'Open Sans', sans-serif;
            font-size: 0.75rem;
            font-weight: 500;
        }

        .domain-option:hover {
            background: rgba(255, 255, 255, 0.1);
            color: #f42e8d;
        }
        
        .floating-settings-button .lucida-icon.disabled {
            filter: grayscale(100%);
            cursor: not-allowed;
            opacity: 0.3;
        }
        
        .lucida-modal-container {
            position: fixed;
            top: calc(10% - 0.25rem);
            right: 3.5rem;
            display: block;
            z-index: 10000;
            pointer-events: none;
        }
        
        .lucida-modal-container.show {
            pointer-events: auto;
        }
        
        .lucida-modal {
            background: #222;
            color: #fff;
            border-radius: 0.75rem;
            width: 20rem;
            max-width: 90vw;
            box-shadow: 0 0.25rem 1rem rgba(0, 0, 0, 0.3);
            opacity: 0;
            transform: translateX(2rem);
            transition: opacity 0.3s ease, transform 0.3s ease;
            pointer-events: none;
            overflow: hidden;
            font-family: 'Open Sans', sans-serif;
        }
        
        .lucida-modal-container.show .lucida-modal {
            opacity: 1;
            transform: translateX(0);
            pointer-events: auto;
        }
        
        .lucida-modal * {
            font-family: 'Open Sans', sans-serif;
            box-sizing: border-box;
        }
        
        .lucida-modal-header {
            display: flex;
            align-items: center;
            justify-content: space-between;
            padding: 1rem;
            border-bottom: 1px solid rgba(255, 255, 255, 0.1);
        }

        .lucida-modal-header svg {
            transition: color 0.2s ease;
        }

        .lucida-modal-header svg:hover {
            color: #f42e8d !important;
        }
        
        .lucida-modal-header h2 {
            margin: 0;
            font-size: 1.125rem;
            font-weight: 600;
            color: #f42e8d;
        }
        
        .lucida-modal-content {
            padding: 1rem;
        }
        
        .preference-group {
            margin-bottom: 1.25rem;
        }
        
        .preference-group:last-child {
            margin-bottom: 0;
        }
        
        .lucida-modal label {
            display: block;
            margin-bottom: 0.5rem;
            font-weight: 500;
            font-size: 0.875rem;
            color: #ccc;
        }
        
        .lucida-modal select {
            width: 100%;
            padding: 0.5rem;
            background: #333;
            border: 1px solid #444;
            border-radius: 0.25rem;
            color: #fff;
            font-size: 0.875rem;
            appearance: none;
            background-image: 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='%23ccc' d='M6 8L1 3h10z'/%3E%3C/svg%3E");
            background-repeat: no-repeat;
            background-position: calc(100% - 0.75rem) center;
            font-family: 'Open Sans', sans-serif;
            cursor: pointer;
            transition: border-color 0.2s ease, background-color 0.2s ease;
        }

        .lucida-modal select:hover {
            border-color: #f42e8d;
            background-color: #3a3a3a;
        }

        .lucida-modal select:focus {
            outline: none;
            border-color: #f42e8d;
        }
        
        .service-select-wrapper {
            position: relative;
            margin-bottom: 1rem;
        }
        
        .custom-select {
            width: 100%;
            padding: 0.5rem;
            background: #333;
            border: 1px solid #444;
            border-radius: 0.25rem;
            font-size: 0.875rem;
            color: #fff;
            display: flex;
            align-items: center;
            gap: 0.5rem;
            cursor: pointer;
            background-image: 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='%23ccc' d='M6 8L1 3h10z'/%3E%3C/svg%3E");
            background-repeat: no-repeat;
            background-position: calc(100% - 0.75rem) center;
            font-family: 'Open Sans', sans-serif;
            transition: border-color 0.2s ease, background-color 0.2s ease;
        }

        .custom-select:hover {
            border-color: #f42e8d;
            background-color: #3a3a3a;
        }
        
        .custom-options {
            position: absolute;
            top: 100%;
            left: 0;
            right: 0;
            background: #333;
            border: 1px solid #444;
            border-radius: 0.25rem;
            margin-top: 0.25rem;
            max-height: 12.5rem;
            overflow-y: auto;
            z-index: 1000;
            display: none;
            box-shadow: 0 0.25rem 0.5rem rgba(0, 0, 0, 0.3);
        }
        
        .custom-options.show {
            display: block;
        }

        .service-status {
            display: inline-block;
            width: 8px;
            height: 8px;
            border-radius: 50%;
            margin-left: 5px;
            background-color: #888;
        }

        .service-status.online {
            background-color: #4CAF50;
        }

        .service-status.offline {
            background-color: #F44336;
        }
        
        .service-option {
            display: flex;
            align-items: center;
            gap: 0.5rem;
            padding: 0.5rem;
            cursor: pointer;
            transition: background-color 0.2s ease;
            color: #fff;
            font-size: 0.875rem;
            font-family: 'Open Sans', sans-serif;
        }

        .service-option:hover {
            background-color: #444;
        }
        
        .service-option img,
        .custom-select img {
            width: 1rem;
            height: 1rem;
            object-fit: contain;
        }
        
        [role='grid'] {
            margin-left: 3.125rem;
        }
    
        [data-testid="tracklist-row"] {
            position: relative;
        }
    
        [role="presentation"] > * {
            contain: unset;
        }
    
        .btn {
            width: 2.5rem;
            height: 2.5rem;
            border-radius: 50%;
            border: 0;
            position: relative;
            cursor: pointer;
            transition: all 0.2s ease;
            box-shadow: 0 0.125rem 0.3125rem 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 0.25rem 0.5rem 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: -1.25rem;
            margin-right: 0.625rem;
        }
    
        .N7GZp8IuWPJvCPz_7dOg .btn {
            width: 1.5rem;
            height: 1.5rem;
            transform-origin: center;
            position: absolute;
            top: 50%;
            right: 100%;
            margin-top: -0.75rem !important;
            margin-right: 0.625rem;
        }
    
        .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);
        
        if (SERVICE_STATUS.hasOwnProperty(value)) {
            const status = document.createElement('span');
            status.className = 'service-status';
            status.dataset.service = value;
            status.classList.add(SERVICE_STATUS[value] ? 'online' : 'offline');
            option.appendChild(status);
        }
        
        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>`;
        }
        
        if (SERVICE_STATUS.hasOwnProperty(value)) {
            const statusClass = SERVICE_STATUS[value] ? 'online' : 'offline';
            content += `<span class="service-status ${statusClass}" data-service="${value}"></span>`;
        }
        
        customSelect.innerHTML = content;
    }

    function checkServiceStatus() {
        const domain = GM_getValue('domainPreference', 'random') === 'random' 
            ? DOMAINS[Math.floor(Math.random() * DOMAINS.length)]
            : GM_getValue('domainPreference');
        
        GM_xmlhttpRequest({
            method: 'GET',
            url: `https://${domain}/api/stats`,
            onload: function(response) {
                try {
                    const data = JSON.parse(response.responseText);
                    if (data && data.all && data.all.downloads && data.all.downloads.current && data.all.downloads.current.services) {
                        const services = data.all.downloads.current.services;
                        
                        SERVICE_STATUS.tidal = services.tidal > 0;
                        SERVICE_STATUS.soundcloud = services.soundcloud > 0;
                        SERVICE_STATUS.deezer = services.deezer > 0;
                        SERVICE_STATUS.amazon = services.amazon > 0;
                        
                        updateServiceStatusIndicators();
                    }
                } catch (e) {
                    console.error('Error parsing Lucida stats:', e);
                }
            },
            onerror: function(error) {
                console.error('Error fetching Lucida stats:', error);
            }
        });
    }

    function updateServiceStatusIndicators() {
        Object.keys(SERVICE_STATUS).forEach(service => {
            const indicators = document.querySelectorAll(`.service-status[data-service="${service}"]`);
            indicators.forEach(indicator => {
                indicator.classList.remove('online', 'offline');
                indicator.classList.add(SERVICE_STATUS[service] ? 'online' : 'offline');
            });
        });
    }

    function createFloatingButton() {
        const button = document.createElement('div');
        button.className = 'floating-settings-button';
        
        const lucidaIcon = document.createElement('img');
        lucidaIcon.src = 'https://raw.githubusercontent.com/afkarxyz/userscripts/refs/heads/main/assets/lucida/lucida.png';
        lucidaIcon.alt = 'Lucida';
        lucidaIcon.className = 'lucida-icon';
        
        const buttonMenu = document.createElement('div');
        buttonMenu.className = 'button-menu';
        
        const settingsMenuItem = document.createElement('div');
        settingsMenuItem.className = 'menu-item';
        settingsMenuItem.innerHTML = `<svg viewBox="0 0 24 24"><path d="M12,15.5A3.5,3.5 0 0,1 8.5,12A3.5,3.5 0 0,1 12,8.5A3.5,3.5 0 0,1 15.5,12A3.5,3.5 0 0,1 12,15.5M19.43,12.97C19.47,12.65 19.5,12.33 19.5,12C19.5,11.67 19.47,11.34 19.43,11L21.54,9.37C21.73,9.22 21.78,8.95 21.66,8.73L19.66,5.27C19.54,5.05 19.27,4.96 19.05,5.05L16.56,6.05C16.04,5.66 15.5,5.32 14.87,5.07L14.5,2.42C14.46,2.18 14.25,2 14,2H10C9.75,2 9.54,2.18 9.5,2.42L9.13,5.07C8.5,5.32 7.96,5.66 7.44,6.05L4.95,5.05C4.73,4.96 4.46,5.05 4.34,5.27L2.34,8.73C2.21,8.95 2.27,9.22 2.46,9.37L4.57,11C4.53,11.34 4.5,11.67 4.5,12C4.5,12.33 4.53,12.65 4.57,12.97L2.46,14.63C2.27,14.78 2.21,15.05 2.34,15.27L4.34,18.73C4.46,18.95 4.73,19.03 4.95,18.95L7.44,17.94C7.96,18.34 8.5,18.68 9.13,18.93L9.5,21.58C9.54,21.82 9.75,22 10,22H14C14.25,22 14.46,21.82 14.5,21.58L14.87,18.93C15.5,18.67 16.04,18.34 16.56,17.94L19.05,18.95C19.27,19.03 19.54,18.95 19.66,18.73L21.66,15.27C21.78,15.05 21.73,14.78 21.54,14.63L19.43,12.97Z"></path></svg>`;
        
        const toOption = document.createElement('div');
        toOption.className = 'domain-option';
        toOption.textContent = '.to';
        
        const suOption = document.createElement('div');
        suOption.className = 'domain-option';
        suOption.textContent = '.su';
        
        buttonMenu.appendChild(settingsMenuItem);
        buttonMenu.appendChild(toOption);
        buttonMenu.appendChild(suOption);
        
        button.appendChild(lucidaIcon);
        button.appendChild(buttonMenu);
        document.body.appendChild(button);
        
        const modalContainer = document.createElement('div');
        modalContainer.className = 'lucida-modal-container';
        document.body.appendChild(modalContainer);
    
        lucidaIcon.addEventListener('click', (e) => {
            if (lucidaIcon.classList.contains('disabled')) {
                e.preventDefault();
                e.stopPropagation();
                return false;
            }
            
            if (!lucidaIcon.classList.contains('disabled')) {
                const currentUrl = window.location.href;
                const domain = GM_getValue('domainPreference', 'random') === 'random' 
                    ? DOMAINS[Math.floor(Math.random() * DOMAINS.length)]
                    : GM_getValue('domainPreference');
                
                window.open(`https://${domain}/?url=${encodeURIComponent(currentUrl)}&country=auto`, '_blank');
            }
            e.stopPropagation();
        });
    
        settingsMenuItem.addEventListener('click', (e) => {
            createPreferencesModal(modalContainer);
            requestAnimationFrame(() => {
                modalContainer.classList.add('show');
            });
            e.preventDefault();
            e.stopPropagation();
            return false; 
        });
        
        toOption.addEventListener('click', (e) => {
            window.open('https://lucida.to/stats', '_blank');
            e.preventDefault();
            e.stopPropagation();
            return false; 
        });
        
        suOption.addEventListener('click', (e) => {
            window.open('https://lucida.su/stats', '_blank');
            e.preventDefault();
            e.stopPropagation();
            return false; 
        });
    
        if (window.location.hostname === 'open.spotify.com') {
            lucidaIcon.classList.add('disabled');
            lucidaIcon.addEventListener('click', (e) => {
                if (lucidaIcon.classList.contains('disabled')) {
                    e.preventDefault();
                    e.stopPropagation();
                    return false;
                }
            }, true);
        }
    
        if (window.location.hostname.includes('lucida.')) {
            button.style.display = 'none';
        }
    
        document.addEventListener('click', (e) => {
            if (!e.target.closest('.lucida-modal') && !e.target.closest('.menu-item') && modalContainer.classList.contains('show')) {
                modalContainer.classList.remove('show');
            }
        });
    
        return button;
    }
    
    function createPreferencesModal(modalContainer) {
        modalContainer.innerHTML = '';
        
        const modal = document.createElement('div');
        modal.className = 'lucida-modal';
        
        const modalHeader = document.createElement('div');
        modalHeader.className = 'lucida-modal-header';
        
        const modalTitle = document.createElement('h2');
        modalTitle.textContent = 'Lucida Preferences';
        
        const closeButton = document.createElement('div');
        closeButton.innerHTML = `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 6L6 18M6 6l12 12"></path></svg>`;
        closeButton.style.cursor = 'pointer';
        closeButton.style.color = '#ccc';
        
        closeButton.addEventListener('click', () => {
            modalContainer.classList.remove('show');
        });
        
        modalHeader.appendChild(modalTitle);
        modalHeader.appendChild(closeButton);
        
        const modalContent = document.createElement('div');
        modalContent.className = 'lucida-modal-content';
        
        const domainGroup = document.createElement('div');
        domainGroup.className = 'preference-group';
        
        const domainLabel = document.createElement('label');
        domainLabel.textContent = 'Domain';
        domainLabel.setAttribute('for', 'domain-select');
        
        const domainSelect = document.createElement('select');
        domainSelect.id = 'domain-select';
        domainSelect.innerHTML = `
            <option value="random">Random</option>
            <option value="lucida.to">Lucida.to</option>
            <option value="lucida.su">Lucida.su</option>
        `;
        
        domainGroup.appendChild(domainLabel);
        domainGroup.appendChild(domainSelect);
        
        const serviceGroup = document.createElement('div');
        serviceGroup.className = 'preference-group';
        
        const serviceLabel = document.createElement('label');
        serviceLabel.textContent = 'Spotify Service Resolver';
        
        const serviceSelectWrapper = document.createElement('div');
        serviceSelectWrapper.className = 'service-select-wrapper';
        
        const customSelect = document.createElement('div');
        customSelect.className = 'custom-select';
        customSelect.id = 'custom-service-select';
        customSelect.innerHTML = '<span>Select a service</span>';
        
        const customOptions = document.createElement('div');
        customOptions.className = 'custom-options';
        
        const serviceInput = document.createElement('input');
        serviceInput.type = 'hidden';
        serviceInput.id = 'service-select';
        
        serviceSelectWrapper.appendChild(customSelect);
        serviceSelectWrapper.appendChild(customOptions);
        serviceSelectWrapper.appendChild(serviceInput);
        
        serviceGroup.appendChild(serviceLabel);
        serviceGroup.appendChild(serviceSelectWrapper);
        
        const formatGroup = document.createElement('div');
        formatGroup.className = 'preference-group';
        
        const formatLabel = document.createElement('label');
        formatLabel.textContent = 'Download Format';
        formatLabel.setAttribute('for', 'format-select');
        
        const formatSelect = document.createElement('select');
        formatSelect.id = 'format-select';
        formatSelect.innerHTML = `
            <option value="original">Original Format (Highest Quality)</option>
            <option value="flac">FLAC</option>
            <option value="mp3">MP3</option>
            <option value="ogg-vorbis">OGG Vorbis</option>
            <option value="opus">Opus</option>
            <option value="m4a-aac">M4A AAC</option>
            <option value="wav">WAV</option>
            <option value="bitcrush">Bitcrush</option>
        `;
        
        formatGroup.appendChild(formatLabel);
        formatGroup.appendChild(formatSelect);
        
        const qualityContainer = document.createElement('div');
        qualityContainer.id = 'quality-settings-container';
        qualityContainer.style.display = 'none';
        qualityContainer.style.marginTop = '1rem';
        
        const qualityLabel = document.createElement('label');
        qualityLabel.textContent = 'Quality Settings';
        qualityLabel.setAttribute('for', 'quality-select');
        qualityLabel.style.marginTop = '0';
        
        const qualitySelect = document.createElement('select');
        qualitySelect.id = 'quality-select';
        
        qualityContainer.appendChild(qualityLabel);
        qualityContainer.appendChild(qualitySelect);
        
        formatGroup.appendChild(qualityContainer);
        
        const autoDownloadGroup = document.createElement('div');
        autoDownloadGroup.className = 'preference-group';
        
        const autoDownloadLabel = document.createElement('label');
        autoDownloadLabel.textContent = 'Auto Download';
        autoDownloadLabel.setAttribute('for', 'auto-download-select');
        
        const autoDownloadSelect = document.createElement('select');
        autoDownloadSelect.id = 'auto-download-select';
        autoDownloadSelect.innerHTML = `
            <option value="enabled">Enabled</option>
            <option value="disabled">Disabled</option>
        `;
        
        autoDownloadGroup.appendChild(autoDownloadLabel);
        autoDownloadGroup.appendChild(autoDownloadSelect);
        
        modalContent.appendChild(domainGroup);
        modalContent.appendChild(serviceGroup);
        modalContent.appendChild(formatGroup);
        modalContent.appendChild(autoDownloadGroup);
        
        modal.appendChild(modalHeader);
        modal.appendChild(modalContent);
        
        modalContainer.appendChild(modal);
        
        domainSelect.value = GM_getValue('domainPreference', 'random');
        formatSelect.value = GM_getValue('formatPreference', 'original');
        autoDownloadSelect.value = GM_getValue('autoDownloadEnabled', 'enabled');
        
        Object.entries(SERVICES).forEach(([value, service]) => {
            const option = createServiceOption(value, service);
            customOptions.appendChild(option);
            
            option.addEventListener('click', () => {
                serviceInput.value = value;
                GM_setValue('targetService', value);
                updateCustomSelect(customSelect, value);
                customOptions.classList.remove('show');
            });
        });
        
        const savedService = GM_getValue('targetService', 'tidal');
        if (SERVICES[savedService]) {
            updateCustomSelect(customSelect, savedService);
            serviceInput.value = savedService;
        } else {
            updateCustomSelect(customSelect, 'tidal');
            serviceInput.value = 'tidal';
        }
        
        customSelect.addEventListener('click', () => {
            customOptions.classList.toggle('show');
        });
        
        function updateQualityOptions(format) {
            qualitySelect.innerHTML = '';
            
            switch(format) {
                case 'flac':
                    qualitySelect.innerHTML = '<option value="16">16-bit 44.1kHz</option>';
                    qualityContainer.style.display = 'block';
                    GM_setValue('qualityPreference', '16');
                    break;
                case 'mp3':
                case 'ogg-vorbis':
                case 'm4a-aac':
                    qualitySelect.innerHTML = `
                        <option value="320">320kb/s</option>
                        <option value="256">256kb/s</option>
                        <option value="192">192kb/s</option>
                        <option value="128">128kb/s</option>
                    `;
                    qualityContainer.style.display = 'block';
                    GM_setValue('qualityPreference', '320');
                    break;
                case 'opus':
                    qualitySelect.innerHTML = `
                        <option value="320">320kb/s</option>
                        <option value="256">256kb/s</option>
                        <option value="192">192kb/s</option>
                        <option value="128">128kb/s</option>
                        <option value="96">96kb/s</option>
                        <option value="64">64kb/s</option>
                    `;
                    qualityContainer.style.display = 'block';
                    GM_setValue('qualityPreference', '320');
                    break;
                default:
                    qualityContainer.style.display = 'none';
                    GM_setValue('qualityPreference', null);
            }
        }
        
        updateQualityOptions(formatSelect.value);
        
        if (qualitySelect) {
            qualitySelect.value = GM_getValue('qualityPreference', '320');
            qualitySelect.addEventListener('change', () => {
                GM_setValue('qualityPreference', qualitySelect.value);
            });
        }
        
        domainSelect.addEventListener('change', () => {
            GM_setValue('domainPreference', domainSelect.value);
        });
        
        formatSelect.addEventListener('change', () => {
            GM_setValue('formatPreference', formatSelect.value);
            updateQualityOptions(formatSelect.value);
        });
        
        autoDownloadSelect.addEventListener('change', () => {
            GM_setValue('autoDownloadEnabled', autoDownloadSelect.value);
        });
        
        document.addEventListener('click', (e) => {
            if (!e.target.closest('.service-select-wrapper')) {
                customOptions.classList.remove('show');
            }
        });
    }

    function autoSelectFormat() {
        if (!window.location.hostname.includes('lucida.')) return;

        const selectFormatAndQuality = () => {
            const convertSelect = document.getElementById('convert');
            if (!convertSelect) return;

            const format = GM_getValue('formatPreference', 'original');
            const quality = GM_getValue('qualityPreference', '320');

            convertSelect.value = format;
            convertSelect.dispatchEvent(new Event('change', { bubbles: true }));

            const observer = new MutationObserver((mutations, obs) => {
                const downsettingSelect = document.getElementById('downsetting');
                if (downsettingSelect) {
                    downsettingSelect.value = quality;
                    downsettingSelect.dispatchEvent(new Event('change', { bubbles: true }));
                    obs.disconnect();
                }
            });

            observer.observe(document.body, { childList: true, subtree: true });
        };

        if (document.getElementById('convert')) {
            selectFormatAndQuality();
        }

        const pageObserver = new MutationObserver(() => {
            if (document.getElementById('convert')) {
                selectFormatAndQuality();
            }
        });

        pageObserver.observe(document.body, { childList: true, subtree: true });
    }

    function autoDownload() {
        if (!window.location.hostname.includes('lucida.')) return;
        if (GM_getValue('autoDownloadEnabled', 'enabled') !== 'enabled') return;

        const clickDownloadButton = () => {
            const button = document.querySelector('.d1-track button') || 
                          document.querySelector('button[class*="download-button"]');
            
            if (button) {
                button.click();
            }
        };

        const observer = new MutationObserver(() => {
            clickDownloadButton();
        });

        observer.observe(document.body, {
            childList: true,
            subtree: true
        });

        clickDownloadButton();
    }
    
    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', 'tidal'),
        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);
    }
    
    function initialize() {
        const floatingButton = createFloatingButton();
        requestAnimationFrame(animateLoop);
        autoSelectFormat();
        autoDownload();
    
        checkServiceStatus();
        setInterval(checkServiceStatus, 5000);
    
        const isLucidaDomain = window.location.hostname.includes('lucida.');
        if (GM_getValue('floatIconEnabled', 'enabled') === 'disabled' || isLucidaDomain) {
            floatingButton.style.display = 'none';
        }
    }

    initialize();
})();