Youtube Video Downloader

Simple Youtube Video Downloader

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

// ==UserScript==
// @name         Youtube Video Downloader
// @namespace    http://tampermonkey.net/
// @author       fb
// @version      1.1
// @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('#end');
        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.marginRight = "10px";

        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 style = document.createElement('style');
        style.textContent = `
:root {
    --btn-bg:           #272727;
    --btn-hover-bg:     #3f3f3f;
    --btn-color:        #fff;
    --btn-radius:       18px;
    --btn-padding:      0 20px;
    --btn-font:         500 14px/36px "Roboto","Arial",sans-serif;
    --btn-cursor:       pointer;
  }

  #download-button,
  #format {
    display: inline-flex;
    align-items: center;
    color: var(--btn-color);
    background-color: var(--btn-bg);
    border: none;
    border-radius: var(--btn-radius);
    padding: var(--btn-padding);
    white-space: nowrap;
    text-transform: none;
    font: var(--btn-font);
    cursor: var(--btn-cursor);
    transition: background-color .2s ease;
  }

  #download-button:hover,
  #format:hover {
    background-color: var(--btn-hover-bg);
  }

  #download-button {
    padding-left: 40px;
    background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' stroke='white' stroke-width='0.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 4v12'/%3E%3Cpath d='M8 12l4 4 4-4'/%3E%3Cpath d='M4 18h16'/%3E%3C/g%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: 8px center;
    background-size: 28px;
  }

  #format {
    appearance: none;
    -webkit-appearance: none;
    -moz-appearance: none;
    padding-right: 40px;
    background-image:
      url("data:image/svg+xml;charset=US-ASCII,%3Csvg%20width%3D'10'%20height%3D'7'%20xmlns%3D'http%3A//www.w3.org/2000/svg'%3E%3Cpath%20d%3D'M0%200l5%207%205-7z'%20fill%3D'%23fff'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 12px center;
  }

  #format::-ms-expand {
    display: none;
  }


        `;
        document.head.appendChild(style);

        const btn = document.createElement('button');
        btn.id = 'download-button';
        btn.textContent = 'Download';

        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;
        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';
        }
    }
})();