YouSnatch

Adds a Download button to YouTube for MP4 and DASH formats with optional stream selection.

目前為 2025-05-03 提交的版本,檢視 最新版本

// ==UserScript==
// @name         YouSnatch
// @namespace    http://tampermonkey.net/
// @version      1.1
// @description  Adds a Download button to YouTube for MP4 and DASH formats with optional stream selection.
// @author       JourneysFootpath
// @match        https://www.youtube.com/watch*
// @grant        none
// @license MIT
// ==/UserScript==

(function() {
    'use strict';

    let advancedMode = false;
    let injected = false;

    document.addEventListener('DOMContentLoaded', () => {
        const playerResponse = unsafeWindow.ytInitialPlayerResponse;
        const actions = document.querySelector('#top-level-buttons-computed');
        if (playerResponse && actions && !injected) {
            injected = true;
            addToggle(actions);
            renderButton(playerResponse, actions);
        }
    });

    function addToggle(container) {
        const toggle = createButton('Switch to Advanced Mode', 'yt-spec-button-shape-next', () => {
            advancedMode = !advancedMode;
            toggle.innerText = advancedMode ? 'Switch to Basic Mode' : 'Switch to Advanced Mode';
            const wrapper = document.getElementById('yt-download-wrapper');
            if (wrapper) wrapper.remove();
            renderButton(unsafeWindow.ytInitialPlayerResponse, container);
        });
        container.appendChild(toggle);
    }

    function createButton(text, className, onClick) {
        const button = document.createElement('button');
        button.innerText = text;
        button.className = className;
        button.style.marginLeft = '8px';
        button.onclick = onClick;
        return button;
    }

    function renderButton(playerResponse, container) {
        const wrapper = document.createElement('div');
        wrapper.id = 'yt-download-wrapper';
        wrapper.style.marginLeft = '8px';

        if (!advancedMode) {
            const format = getBestFormat(playerResponse);
            if (format) {
                const button = createDownloadButton(format.url, `⬇️ Download (${format.qualityLabel})`);
                wrapper.appendChild(button);
            } else {
                wrapper.innerText = 'No MP4 stream available';
            }
        } else {
            const { videoOptions, audioOptions } = getDASHOptions(playerResponse);
            if (videoOptions.length || audioOptions.length) {
                const videoSelect = createSelectElement(videoOptions, '🎥 Video: ');
                const audioSelect = createSelectElement(audioOptions, '🔊 Audio: ');

                const videoLink = createDownloadLink('⬇️ Download Video', videoSelect);
                const audioLink = createDownloadLink('⬇️ Download Audio', audioSelect);

                wrapper.appendChild(videoSelect.label);
                wrapper.appendChild(videoSelect.element);
                wrapper.appendChild(document.createElement('br'));
                wrapper.appendChild(audioSelect.label);
                wrapper.appendChild(audioSelect.element);
                wrapper.appendChild(document.createElement('br'));
                wrapper.appendChild(videoLink);
                wrapper.appendChild(audioLink);
            } else {
                wrapper.innerText = 'No DASH streams available';
            }
        }

        container.appendChild(wrapper);
    }

    function createDownloadButton(url, text) {
        const button = document.createElement('a');
        button.href = url;
        button.innerText = text;
        button.download = '';
        button.target = '_blank';
        applyButtonStyles(button);
        return button;
    }

    function createDownloadLink(text, selectElement) {
        const link = document.createElement('a');
        link.innerText = text;
        link.download = '';
        link.target = '_blank';
        link.style.margin = '4px';
        link.style.display = 'inline-block';
        link.style.background = '#2196F3';
        link.style.color = '#fff';
        link.style.padding = '4px 10px';
        link.style.borderRadius = '4px';
        link.style.textDecoration = 'none';

        selectElement.onchange = () => link.href = selectElement.value;
        link.href = selectElement.value; // Set initial value
        return link;
    }

    function applyButtonStyles(button) {
        button.style.padding = '6px 12px';
        button.style.background = '#4CAF50';
        button.style.color = '#fff';
        button.style.border = 'none';
        button.style.borderRadius = '4px';
        button.style.cursor = 'pointer';
    }

    function createSelectElement(options, labelText) {
        const select = document.createElement('select');
        const label = document.createElement('label');
        label.innerText = labelText;
        options.forEach(f => {
            const opt = document.createElement('option');
            opt.value = f.url;
            opt.text = `${f.qualityLabel || f.audioQuality}`;
            select.appendChild(opt);
        });
        return { label, element: select };
    }

    function getBestFormat(playerResponse) {
        return (playerResponse.streamingData?.formats || []).find(f =>
            f.mimeType.includes('video/mp4') && f.qualityLabel && f.url);
    }

    function getDASHOptions(playerResponse) {
        const adaptive = playerResponse.streamingData?.adaptiveFormats || [];
        const videoOptions = adaptive.filter(f => f.mimeType.includes('video/mp4') && f.qualityLabel);
        const audioOptions = adaptive.filter(f => f.mimeType.includes('audio/mp4'));
        return { videoOptions, audioOptions };
    }
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址