YouTube MP3 下载器

YouTube to MP3 converter with clean interface

// ==UserScript==
// @name         YouTube MP3 下载器
// @namespace    http://tampermonkey.net/
// @version      1.0.1
// @description  YouTube to MP3 converter with clean interface
// @match        https://www.youtube.com/*
// @grant        GM_xmlhttpRequest
// @connect      loader.to
// @connect      p.oceansaver.in
// ==/UserScript==

(function () {
    'use strict';

    // 创建下载按钮
    function createDownloadButton() {
        const trigger = document.createElement('div');
        trigger.id = 'yt-mp3-trigger';

        const button = document.createElement('button');
        button.id = 'yt-mp3-download';

        const text = document.createTextNode('⬇');  // 使用已验证的下载图标
        button.appendChild(text);

        button.addEventListener('click', showDownloadPanel);

        const wrapper = document.createDocumentFragment();
        wrapper.appendChild(trigger);
        wrapper.appendChild(button);

        return wrapper;
    }

    // 添加样式
    function addStyles() {
        const styleElement = document.createElement('style');
        styleElement.textContent = `
            #yt-mp3-download {
                position: fixed !important;
                bottom: -30px !important;
                left: 20px !important;
                width: 40px !important;
                height: 40px !important;
                background-color: rgba(0, 0, 0, 0.6) !important;
                color: white !important;
                border: none !important;
                border-radius: 50% !important;
                cursor: pointer !important;
                opacity: 0 !important;
                transition: all 0.3s ease !important;
                display: flex !important;
                align-items: center !important;
                justify-content: center !important;
                font-size: 20px !important;
                z-index: 9999999 !important;
                padding: 0 !important;
                margin: 0 !important;
                box-shadow: 0 2px 5px rgba(0,0,0,0.2) !important;
            }
            #yt-mp3-trigger {
                position: fixed !important;
                bottom: 0 !important;
                left: 0 !important;
                width: 100px !important;
                height: 60px !important;
                z-index: 9999998 !important;
            }
            #yt-mp3-trigger:hover + #yt-mp3-download,
            #yt-mp3-download:hover {
                bottom: 20px !important;
                opacity: 0.8 !important;
            }
            #yt-mp3-download:hover {
                transform: scale(1.1) !important;
                opacity: 1 !important;
            }
            #yt-mp3-panel {
                position: fixed !important;
                bottom: 70px !important;
                left: 20px !important;
                background: rgba(33, 33, 33, 0.95) !important;
                border-radius: 12px !important;
                padding: 15px !important;
                color: white !important;
                font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif !important;
                font-size: 14px !important;
                min-width: 200px !important;
                box-shadow: 0 4px 12px rgba(0,0,0,0.2) !important;
                backdrop-filter: blur(10px) !important;
                z-index: 9999997 !important;
                transform: translateX(0);
                transition: transform 0.3s ease;
            }
            .download-progress {
                width: 100% !important;
                height: 4px !important;
                background: rgba(255,255,255,0.1) !important;
                border-radius: 2px !important;
                margin: 10px 0 !important;
                overflow: hidden !important;
            }
            .progress-bar {
                width: 0% !important;
                height: 100% !important;
                background: #FF0000 !important;
                transition: width 0.3s ease !important;
            }
            .download-info {
                margin-top: 8px !important;
                font-size: 12px !important;
                color: rgba(255,255,255,0.7) !important;
            }
            .download-button {
                background: #FF0000 !important;
                color: white !important;
                border: none !important;
                padding: 8px 16px !important;
                border-radius: 6px !important;
                cursor: pointer !important;
                width: 100% !important;
                margin-top: 10px !important;
                font-weight: 500 !important;
                transition: background 0.3s ease !important;
            }
            .download-button:hover {
                background: #FF1a1a !important;
            }
        `;
        document.head.appendChild(styleElement);
    }

    // 显示下载面板
    function showDownloadPanel() {
        const videoId = new URLSearchParams(window.location.search).get('v');
        if (!videoId) {
            alert('Please open a YouTube video first.');
            return;
        }

        // 移除已存在的面板
        const existingPanel = document.getElementById('yt-mp3-panel');
        if (existingPanel) {
            existingPanel.remove();
            return;
        }

        const panel = document.createElement('div');
        panel.id = 'yt-mp3-panel';

        // 添加标题栏
        const headerBar = document.createElement('div');
        headerBar.style.cssText = `
            width: 100%;
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 10px;
            cursor: move;
        `;

        const title = document.createElement('div');
        title.textContent = 'MP3 Download';
        title.style.fontSize = '14px';

        const closeButton = document.createElement('button');
        closeButton.textContent = '×';
        closeButton.style.cssText = `
            background: none;
            border: none;
            color: white;
            cursor: pointer;
            padding: 0 6px;
            font-size: 16px;
        `;
        closeButton.onclick = () => document.body.removeChild(panel);

        headerBar.appendChild(title);
        headerBar.appendChild(closeButton);
        panel.appendChild(headerBar);

        // 添加进度条
        const progress = document.createElement('div');
        progress.className = 'download-progress';
        const progressBar = document.createElement('div');
        progressBar.className = 'progress-bar';
        progress.appendChild(progressBar);
        progress.style.display = 'none';
        panel.appendChild(progress);

        // 添加状态信息
        const info = document.createElement('div');
        info.className = 'download-info';
        panel.appendChild(info);

        // 添加下载按钮
        const downloadButton = document.createElement('button');
        downloadButton.className = 'download-button';
        downloadButton.textContent = 'Start Download';
        downloadButton.onclick = () => startDownload(videoId, progressBar, info, downloadButton);
        panel.appendChild(downloadButton);

        document.body.appendChild(panel);

        // 添加拖动功能
        let isDragging = false;
        let currentX, currentY, initialX, initialY, xOffset = 0, yOffset = 0;

        headerBar.addEventListener('mousedown', dragStart);
        document.addEventListener('mousemove', drag);
        document.addEventListener('mouseup', dragEnd);

        function dragStart(e) {
            initialX = e.clientX - xOffset;
            initialY = e.clientY - yOffset;
            if (e.target === headerBar) {
                isDragging = true;
            }
        }

        function drag(e) {
            if (isDragging) {
                e.preventDefault();
                currentX = e.clientX - initialX;
                currentY = e.clientY - initialY;
                xOffset = currentX;
                yOffset = currentY;
                setTranslate(currentX, currentY, panel);
            }
        }

        function setTranslate(xPos, yPos, el) {
            el.style.transform = `translate(${xPos}px, ${yPos}px)`;
        }

        function dragEnd() {
            initialX = currentX;
            initialY = currentY;
            isDragging = false;
        }
    }

    // 开始下载
    function startDownload(videoId, progressBar, info, button) {
        const videoUrl = `https://www.youtube.com/watch?v=${videoId}`;
        const API_KEY = 'a4016fa228909c2bd7cb02037ca2a34c815d03a7';

        button.disabled = true;
        button.textContent = 'Preparing...';
        progressBar.parentElement.style.display = 'block';
        info.textContent = 'Initializing download...';

        GM_xmlhttpRequest({
            method: 'GET',
            url: `https://loader.to/ajax/download.php?format=mp3&url=${encodeURIComponent(videoUrl)}&api=${API_KEY}`,
            headers: {
                'Accept': 'application/json',
                'Content-Type': 'application/json',
            },
            onload: function(response) {
                try {
                    const data = JSON.parse(response.responseText);
                    if (data && data.success) {
                        info.textContent = 'Starting conversion...';
                        checkProgress(data.id, progressBar, info, button);
                    } else {
                        info.textContent = 'Failed to start download: ' + (data.error || 'Unknown error');
                        button.textContent = 'Retry';
                        button.disabled = false;
                    }
                } catch (error) {
                    info.textContent = 'Error parsing response: ' + error.message;
                    button.textContent = 'Retry';
                    button.disabled = false;
                }
            },
            onerror: function(error) {
                info.textContent = 'Network error: ' + (error.message || 'Connection failed');
                button.textContent = 'Retry';
                button.disabled = false;
            }
        });
    }

    // 检查下载进度
    function checkProgress(downloadId, progressBar, info, button) {
        const progressCheck = setInterval(() => {
            GM_xmlhttpRequest({
                method: 'GET',
                url: `https://p.oceansaver.in/ajax/progress.php?id=${downloadId}`,
                headers: {
                    'Accept': 'application/json',
                    'Content-Type': 'application/json',
                },
                onload: function(response) {
                    try {
                        const data = JSON.parse(response.responseText);
                        if (!data) {
                            clearInterval(progressCheck);
                            info.textContent = 'Error: Invalid response';
                            button.textContent = 'Retry';
                            button.disabled = false;
                            return;
                        }

                        const percent = (data.progress / 10).toFixed(1);
                        progressBar.style.width = `${percent}%`;
                        info.textContent = data.text || `Converting: ${percent}%`;

                        if (data.download_url) {
                            clearInterval(progressCheck);
                            info.textContent = 'Download ready!';
                            button.textContent = 'Download MP3';
                            button.disabled = false;
                            button.onclick = () => {
                                window.location.href = data.download_url;
                            };
                        }
                    } catch (error) {
                        clearInterval(progressCheck);
                        info.textContent = 'Error occurred';
                        button.textContent = 'Retry';
                        button.disabled = false;
                    }
                },
                onerror: function() {
                    clearInterval(progressCheck);
                    info.textContent = 'Network error';
                    button.textContent = 'Retry';
                    button.disabled = false;
                }
            });
        }, 1000);
    }

    // 初始化脚本
    function init() {
        if (!document.getElementById('yt-mp3-download')) {
            addStyles();
            const elements = createDownloadButton();
            document.body.appendChild(elements);
        }
    }

    // 页面加载完成后初始化
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }

    // 监听页面变化
    const observer = new MutationObserver(() => {
        if (!document.getElementById('yt-mp3-download')) {
            init();
        }
    });

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