Youtube Video Downloader

Simple Youtube Video Downloader

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

// ==UserScript==
// @name         Youtube Video Downloader 
// @namespace    http://tampermonkey.net/
// @author       fb
// @version      1.0
// @description  Simple Youtube Video Downloader
// @match        https://www.youtube.com/watch*
// @grant        GM_xmlhttpRequest
// @connect      p.oceansaver.in
// @license      GPL-3.0-or-later
// @run-at       document-end
// ==/UserScript==

(function() {
    'use strict';

    let animInterval = null;
    let originalText = '';

    const observer = new MutationObserver((mutations, obs) => {
        const actionsInner = document.querySelector('#actions-inner');
        if (!actionsInner) return;
        obs.disconnect();
        injectUI(actionsInner);
    });
    observer.observe(document.body, { childList: true, subtree: true });

    function injectUI(container) {
        if (document.getElementById('download-button')) return;

        const wrapper = document.createElement('div');
        wrapper.style.display = 'flex';
        wrapper.style.alignItems = 'center';
        wrapper.style.marginBottom = "2px";

        const select = document.createElement('select');
        select.id = 'format';
        select.className = 'doc';
        select.style.marginRight = '8px';
        select.innerHTML = `
            <optgroup label="Audio">
                <option value="mp3">MP3</option>
                <option value="m4a">M4A</option>
                <option value="webm">WEBM</option>
                <option value="aac">AAC</option>
                <option value="flac">FLAC</option>
                <option value="opus">OPUS</option>
                <option value="ogg">OGG</option>
                <option value="wav">WAV</option>
            </optgroup>
            <optgroup label="Video">
                <option value="360">MP4 (360p)</option>
                <option value="480">MP4 (480p)</option>
                <option value="720">MP4 (720p)</option>
                <option selected value="1080">MP4 (1080p)</option>
                <option value="1440">MP4 (1440p)</option>
                <option value="4k">WEBM (4K)</option>
            </optgroup>
        `;

        const btn = document.createElement('button');
        btn.id = 'download-button';
        btn.textContent = 'Download';
        btn.style.cursor = 'pointer';
        btn.className = 'style-scope ytd-button-renderer';
        btn.addEventListener('click', startDownload);

        wrapper.appendChild(select);
        wrapper.appendChild(btn);
        container.insertAdjacentElement('afterbegin', wrapper);
    }

    function startDownload() {
        const btn = document.getElementById('download-button');
        if (!btn) return;
        const fmt = document.getElementById('format').value;
        const videoUrl = encodeURIComponent(window.location.href);
        const initUrl = `https://p.oceansaver.in/ajax/download.php?format=${fmt}&url=${videoUrl}`;

        startButtonAnimation(btn);

        GM_xmlhttpRequest({
            method: 'GET',
            url: initUrl,
            responseType: 'json',
            onload(res) {
                const data = res.response;
                if (!data || !data.success) {
                    stopButtonAnimation();
                    alert('❌ Failed to initialize download');
                    return;
                }
                pollProgress(data.progress_url);
            },
            onerror() {
                stopButtonAnimation();
                alert('❌ Network error while starting download');
            }
        });
    }

    function pollProgress(progressUrl) {
        const intervalId = setInterval(() => {
            GM_xmlhttpRequest({
                method: 'GET',
                url: progressUrl,
                responseType: 'json',
                onload(res) {
                    const p = res.response;
                    if (!p) {
                        clearInterval(intervalId);
                        stopButtonAnimation();
                        return;
                    }
                    if (p.success) {
                        clearInterval(intervalId);
                        triggerFileDownload(p.download_url);
                        stopButtonAnimation();
                    } else {
                        console.log(`Download progress: ${p.progress || 'unknown'}`);
                    }
                },
                onerror() {
                    console.error('Error polling download progress');
                    clearInterval(intervalId);
                    stopButtonAnimation();
                }
            });
        }, 1500);
    }

    function triggerFileDownload(url) {
        const a = document.createElement('a');
        a.href = url;
        a.download = '';
        document.body.appendChild(a);
        a.click();
        document.body.removeChild(a);
    }

    function startButtonAnimation(btn) {
        originalText = btn.textContent;
        btn.disabled = true;
        let dots = 0;
        animInterval = setInterval(() => {
            dots = (dots + 1) % 4;
            btn.textContent = 'Downloading' + '.'.repeat(dots);
        }, 500);
    }

    function stopButtonAnimation() {
        const btn = document.getElementById('download-button');
        if (animInterval) {
            clearInterval(animInterval);
            animInterval = null;
        }
        if (btn) {
            btn.disabled = false;
            btn.textContent = originalText || 'Download';
        }
    }
})();