Youtube MP4 downloader (Web)

Download button

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Youtube MP4 downloader (Web) 
// @namespace    http://tampermonkey.net/
// @version      82.4
// @description  Download button
// @author       Frank
// @match        https://www.youtube.com/watch*
// @match        https://m.youtube.com/watch*
// @match        https://youtube.com/watch*
// @icon         https://www.youtube.com/favicon.ico
// @grant        GM_openInTab
// @grant        GM.openInTab
// @run-at       document-idle
// @compatible   chrome
// @compatible   firefox
// @compatible   edge
// @compatible   opera
// @compatible   safari
// ==/UserScript==
// @license MIT

(function() {
    'use strict';

    // Wacht tot de pagina geladen is (cross-browser compatible)
    function waitForElement(selector, callback, maxAttempts = 50) {
        let attempts = 0;
        
        const checkElement = () => {
            const element = document.querySelector(selector);
            if (element) {
                callback(element);
                return true;
            }
            
            attempts++;
            if (attempts >= maxAttempts) {
                console.log('YouTube Download Button: Element niet gevonden na ' + maxAttempts + ' pogingen');
                return false;
            }
            
            return false;
        };
        
        // Probeer direct
        if (checkElement()) return;
        
        // Gebruik MutationObserver voor moderne browsers
        if (typeof MutationObserver !== 'undefined') {
            const observer = new MutationObserver((mutations, obs) => {
                if (checkElement()) {
                    obs.disconnect();
                }
            });

            observer.observe(document.body, {
                childList: true,
                subtree: true
            });
            
            // Timeout na 10 seconden
            setTimeout(() => observer.disconnect(), 10000);
        } else {
            // Fallback voor oudere browsers
            const interval = setInterval(() => {
                if (checkElement() || attempts >= maxAttempts) {
                    clearInterval(interval);
                }
            }, 200);
        }
    }

    // Functie om de video ID te krijgen
    function getVideoId() {
        const urlParams = new URLSearchParams(window.location.search);
        return urlParams.get('v');
    }

    // Functie om de download knop te maken
    function createDownloadButton() {
        const videoId = getVideoId();
        if (!videoId) return null;

        // Maak de knop container (zonder YouTube classes)
        const buttonContainer = document.createElement('div');
        buttonContainer.style.marginLeft = '8px';
        buttonContainer.style.display = 'inline-flex';

        // Maak de knop met custom styling
        const button = document.createElement('button');
        button.setAttribute('aria-label', 'Download MP4');
        button.style.display = 'flex';
        button.style.alignItems = 'center';
        button.style.gap = '8px';
        button.style.backgroundColor = '#1976d2';
        button.style.color = 'white';
        button.style.border = 'none';
        button.style.borderRadius = '18px';
        button.style.padding = '10px 16px';
        button.style.cursor = 'pointer';
        button.style.fontSize = '14px';
        button.style.fontWeight = '500';
        button.style.fontFamily = 'Roboto, Arial, sans-serif';
        button.style.transition = 'background-color 0.2s';
        
        // Hover effect
        button.addEventListener('mouseenter', () => {
            button.style.backgroundColor = '#1565c0';
        });
        button.addEventListener('mouseleave', () => {
            button.style.backgroundColor = '#1976d2';
        });

        // Voeg icoon toe (download icon)
        const icon = document.createElement('div');
        icon.style.display = 'flex';
        icon.style.alignItems = 'center';
        icon.innerHTML = `
            <svg height="20" viewBox="0 0 24 24" width="20" focusable="false" style="pointer-events: none; display: block;">
                <path d="M17 18v1H6v-1h11zm-.5-6.6-.7-.7-3.8 3.7V4h-1v10.4l-3.8-3.8-.7.7 5 5 5-4.9z" fill="white"></path>
            </svg>
        `;

        // Voeg tekst container toe
        const textContainer = document.createElement('div');
        textContainer.style.display = 'flex';
        textContainer.style.alignItems = 'center';
        textContainer.style.gap = '6px';

        // Voeg tekst toe
        const text = document.createElement('span');
        text.textContent = 'Download MP4';
        text.style.lineHeight = '1';

        // Voeg groen vinkje toe
        const checkmark = document.createElement('span');
        checkmark.innerHTML = `
            <svg height="18" width="18" viewBox="0 0 24 24" style="display: block;">
                <path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41L9 16.17z" fill="#00d000"></path>
            </svg>
        `;

        textContainer.appendChild(text);
        textContainer.appendChild(checkmark);

        button.appendChild(icon);
        button.appendChild(textContainer);
        buttonContainer.appendChild(button);

        // Voeg click event toe
        button.addEventListener('click', (e) => {
            e.preventDefault();
            e.stopPropagation();
            
            // Haal de volledige YouTube URL op
            const currentUrl = window.location.href;
            
            // Maak de SaveFrom.net URL met de YouTube link in de zoekbalk
            const saveFromUrl = `https://en1.savefrom.net/#url=${encodeURIComponent(currentUrl)}`;
            
            // Open SaveFrom.net in een nieuw tabblad (cross-browser compatible)
            try {
                // Probeer eerst GM_openInTab (Tampermonkey/Greasemonkey)
                if (typeof GM_openInTab !== 'undefined') {
                    GM_openInTab(saveFromUrl, { active: true, insert: true });
                } else if (typeof GM !== 'undefined' && GM.openInTab) {
                    GM.openInTab(saveFromUrl, { active: true, insert: true });
                } else {
                    // Fallback naar window.open voor alle browsers
                    const newWindow = window.open(saveFromUrl, '_blank', 'noopener,noreferrer');
                    if (newWindow) {
                        newWindow.opener = null;
                    }
                }
            } catch (err) {
                // Als alles faalt, gebruik gewone window.open
                window.open(saveFromUrl, '_blank');
            }
        });

        return buttonContainer;
    }

    // Functie om de knop toe te voegen
    function addDownloadButton() {
        // Verwijder bestaande knop als die er al is
        const existingButton = document.getElementById('mp4-download-button');
        if (existingButton) {
            existingButton.remove();
        }

        // Zoek de container met de andere knoppen (like, dislike, share, etc.)
        waitForElement('#actions-inner', (actionsContainer) => {
            // Wacht even voor de knoppen container
            setTimeout(() => {
                const buttonGroup = actionsContainer.querySelector('#top-level-buttons-computed');
                if (buttonGroup) {
                    const downloadButton = createDownloadButton();
                    if (downloadButton) {
                        downloadButton.id = 'mp4-download-button';
                        buttonGroup.appendChild(downloadButton);
                    }
                }
            }, 500);
        });
    }

    // Voeg de knop toe bij het laden
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', addDownloadButton);
    } else {
        addDownloadButton();
    }

    // Voeg de knop opnieuw toe als de URL verandert (bij navigatie binnen YouTube)
    // Werkt in alle browsers
    let lastUrl = location.href;
    
    // MutationObserver voor moderne browsers
    if (typeof MutationObserver !== 'undefined') {
        new MutationObserver(() => {
            const url = location.href;
            if (url !== lastUrl) {
                lastUrl = url;
                setTimeout(addDownloadButton, 1000);
            }
        }).observe(document, { subtree: true, childList: true });
    }
    
    // Fallback: periodieke check voor oudere browsers
    setInterval(() => {
        const url = location.href;
        if (url !== lastUrl) {
            lastUrl = url;
            addDownloadButton();
        }
    }, 2000);

})();