YouTube Direct Downloader

Add a custom download button and provide options to download the video or audio directly from the YouTube page.

// ==UserScript==
// @name         YouTube Direct Downloader
// @description  Add a custom download button and provide options to download the video or audio directly from the YouTube page.
// @icon         https://www.google.com/s2/favicons?sz=64&domain=youtube.com
// @version      1.5
// @author       afkarxyz
// @namespace    https://github.com/afkarxyz/userscripts/
// @supportURL   https://github.com/afkarxyz/userscripts/issues
// @license      MIT
// @match        https://www.youtube.com/*
// @match        https://youtube.com/*
// @grant        GM.xmlHttpRequest
// @grant        GM_download
// @grant        GM.download
// @grant        GM_setValue
// @grant        GM_getValue
// @connect      api.mp3youtube.cc
// @connect      iframe.y2meta-uk.com
// @connect      *
// @run-at       document-end
// ==/UserScript==

(function() {
    'use strict';

    let lastSelectedFormat = GM_getValue('lastSelectedFormat', 'video');
    let lastSelectedVideoQuality = GM_getValue('lastSelectedVideoQuality', '1080');
    let lastSelectedAudioBitrate = GM_getValue('lastSelectedAudioBitrate', '320');

    const API_KEY_URL = 'https://api.mp3youtube.cc/v2/sanity/key';
    const API_CONVERT_URL = 'https://api.mp3youtube.cc/v2/converter';
    
    const REQUEST_HEADERS = {
        "Content-Type": "application/json",
        "Origin": "https://iframe.y2meta-uk.com",
        "Accept": "*/*",
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
    };
    const style = document.createElement('style');
    style.textContent = `
        .ytddl-download-btn {
            width: 36px;
            height: 36px;
            border-radius: 50%;
            display: flex;
            align-items: center;
            justify-content: center;
            cursor: pointer;
            margin-left: 8px;
            transition: background-color 0.2s;
        }
        html[dark] .ytddl-download-btn {
            background-color: #ffffff1a;
        }
        html:not([dark]) .ytddl-download-btn {
            background-color: #0000000d;
        }
        html[dark] .ytddl-download-btn:hover {
            background-color: #ffffff33;
        }
        html:not([dark]) .ytddl-download-btn:hover {
            background-color: #00000014;
        }
        .ytddl-download-btn svg {
            width: 18px;
            height: 18px;
        }
        html[dark] .ytddl-download-btn svg {
            fill: var(--yt-spec-text-primary, #fff);
        }
        html:not([dark]) .ytddl-download-btn svg {
            fill: var(--yt-spec-text-primary, #030303);
        }
        
        .ytddl-dialog {
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: #000000;
            color: #e1e1e1;
            border-radius: 12px;
            box-shadow: 0 0 0 1px rgba(225,225,225,.1), 0 2px 4px 1px rgba(225,225,225,.18);
            font-family: 'IBM Plex Mono', 'Noto Sans Mono Variable', 'Noto Sans Mono', monospace;
            width: 400px;
            z-index: 9999;
            padding: 16px;
        }
          .ytddl-backdrop {
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: rgba(0, 0, 0, 0.5);
            z-index: 9998;
        }
        
        .ytddl-dialog h3 {
            margin: 0 0 16px 0;
            font-size: 18px;
            font-weight: 700;
        }
        
        .quality-options {
            display: grid;
            grid-template-columns: repeat(3, 1fr);
            gap: 8px;
            margin-bottom: 16px;
        }
        
        .quality-option {
            display: flex;
            align-items: center;
            padding: 8px;
            cursor: pointer;
            border-radius: 6px;
        }
        
        .quality-option:hover {
            background: #191919;
        }
        
        .quality-option input[type="radio"] {
            margin-right: 8px;
        }
        
        .download-status {
            text-align: center;
            margin: 16px 0;
            font-size: 12px;
            display: none;
            color: #1ed760;
        }
        
        .button-container {
            display: flex;
            justify-content: center;
            gap: 8px;
            margin-top: 16px;
        }
          .ytddl-button {
            background: transparent;
            border: 1px solid #e1e1e1;
            color: #e1e1e1;
            font-size: 14px;
            font-weight: 500;
            padding: 8px 16px;
            border-radius: 18px;
            cursor: pointer;
            font-family: inherit;
            transition: all 0.2s;
        }
        
        .ytddl-button:hover {
            background: #1ed760;
            border-color: #1ed760;
            color: #000000;
        }
        
        .ytddl-button.cancel:hover {
            background: #f3727f;
            border-color: #f3727f;
            color: #000000;
        }
        
        .format-selector {
            margin-bottom: 16px;
            display: flex;
            gap: 8px;
            justify-content: center;
        }
        
        .format-button {
            background: transparent;
            border: 1px solid #e1e1e1;
            color: #e1e1e1;
            padding: 6px 12px;
            border-radius: 14px;
            cursor: pointer;
            font-family: inherit;
            font-size: 12px;
            transition: all 0.2s ease;
        }
        
        .format-button:hover {
            background: #808080;
            color: #000000;
        }
          .format-button.selected {
            background: #1ed760;
            border-color: #1ed760;
            color: #000000;
        }
          .ytddl-overlay {
            position: fixed;
            top: 20px;
            right: 20px;
            background: rgba(0, 0, 0, 0.9);
            color: #e1e1e1;
            border-radius: 8px;
            padding: 16px;
            width: 350px;
            max-width: 350px;
            z-index: 10000;
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
            font-size: 14px;
            box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
            border: 1px solid rgba(255, 255, 255, 0.1);
            backdrop-filter: blur(10px);
            opacity: 0;
            transform: translateX(100%);
            transition: all 0.3s ease;
        }
        
        .ytddl-overlay.show {
            opacity: 1;
            transform: translateX(0);
        }
  
        
        .ytddl-overlay-content {
            line-height: 1.5;
        }
        
        .ytddl-overlay-status {
            margin-bottom: 8px;
            color: #1ed760;
            font-weight: 500;
        }
        
        .ytddl-overlay-details {
            color: #ccc;
            font-size: 13px;
            margin-bottom: 12px;
        }
          .ytddl-overlay-file-info {
            display: flex;
            justify-content: space-between;
            margin-bottom: 8px;
            font-size: 12px;
        }
        
        .ytddl-overlay-size {
            color: #1ed760;
            font-weight: 500;
        }
        
        .ytddl-overlay-speed {
            color: #ffa500;
            font-weight: 500;
        }
        
        .ytddl-overlay-error {
            color: #ff6b6b;
        }
        
        .ytddl-overlay-success {
            color: #1ed760;
        }
    `;
    document.head.appendChild(style);

    let currentOverlay = null;

    function createOverlay() {
        if (currentOverlay) {
            removeOverlay();
        }        const overlay = document.createElement('div');
        overlay.className = 'ytddl-overlay';
        
        const content = document.createElement('div');
        content.className = 'ytddl-overlay-content';
        
        const status = document.createElement('div');
        status.className = 'ytddl-overlay-status';
        status.textContent = 'Initializing...';
        
        const details = document.createElement('div');
        details.className = 'ytddl-overlay-details';
        details.textContent = 'Preparing download request';
          const fileInfoContainer = document.createElement('div');
        fileInfoContainer.className = 'ytddl-overlay-file-info';
        
        const sizeElement = document.createElement('div');
        sizeElement.className = 'ytddl-overlay-size';
        sizeElement.textContent = 'Size: Calculating...';
        
        const speedElement = document.createElement('div');
        speedElement.className = 'ytddl-overlay-speed';
        speedElement.textContent = 'Speed: -';
        
        fileInfoContainer.appendChild(sizeElement);
        fileInfoContainer.appendChild(speedElement);
        
        content.appendChild(status);
        content.appendChild(details);
        content.appendChild(fileInfoContainer);        
        overlay.appendChild(content);
        
        overlay.addEventListener('click', function(e) {
            if (e.target === overlay) {
                removeOverlay();
            }
        });
        
        document.body.appendChild(overlay);
        
        setTimeout(() => {
            overlay.classList.add('show');
        }, 100);
          currentOverlay = overlay;
        return overlay;
    }

    function updateOverlay(status, details, fileSize = null, downloadSpeed = null, isError = false, isSuccess = false) {
        if (!currentOverlay) return;
        const statusEl = currentOverlay.querySelector('.ytddl-overlay-status');
        const detailsEl = currentOverlay.querySelector('.ytddl-overlay-details');
        const sizeEl = currentOverlay.querySelector('.ytddl-overlay-size');        
        const speedEl = currentOverlay.querySelector('.ytddl-overlay-speed');
        
        if (statusEl) {
            statusEl.textContent = status;
            statusEl.className = 'ytddl-overlay-status';
            if (isError) statusEl.classList.add('ytddl-overlay-error');
            if (isSuccess) statusEl.classList.add('ytddl-overlay-success');
        }
        
        if (detailsEl) {
            detailsEl.textContent = details;
        }
        
        if (sizeEl) {
            if (fileSize !== null) {
                sizeEl.textContent = `Size: ${fileSize}`;
                sizeEl.style.display = 'block';
            } else {
                sizeEl.style.display = 'none';
            }
        }
        
        if (speedEl) {
            if (downloadSpeed !== null) {
                speedEl.textContent = `Speed: ${downloadSpeed}`;
                speedEl.style.display = 'block';
            } else {
                speedEl.style.display = 'none';
            }
        }
        currentOverlay.offsetHeight;
    }

    function removeOverlay() {
        if (currentOverlay) {
            currentOverlay.classList.remove('show');
            setTimeout(() => {
                if (currentOverlay && currentOverlay.parentNode) {
                    currentOverlay.parentNode.removeChild(currentOverlay);
                }
                currentOverlay = null;
            }, 300);
        }
    }
    
    function formatBytes(bytes) {
        if (bytes === 0) return '0 Bytes';
        const k = 1024;
        const sizes = ['Bytes', 'KB', 'MB', 'GB'];
        const i = Math.floor(Math.log(bytes) / Math.log(k));
        return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
    }
    function truncateTitle(title, maxLength = 50) {
        if (!title || title.length <= maxLength) return title;
        return title.substring(0, maxLength - 3) + '...';
    }

    function triggerDirectDownload(url, filename) {
        let downloadStartTime = Date.now();
        
        updateOverlay('Validating download URL', 'Testing download link...', null, null);
        
        GM.xmlHttpRequest({
            method: 'HEAD',
            url: url,
            headers: {
                "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
            },
            onload: function(response) {
                console.log('URL Test Response:', response.status, response.statusText);
                console.log('Content-Length:', response.responseHeaders.match(/content-length:\s*(\d+)/i));
                
                if (response.status === 200 || response.status === 206) {
                    fetchAndDownload(url, filename, downloadStartTime);
                } else {
                    updateOverlay(
                        'Download failed', 
                        `Invalid download URL (Status: ${response.status})`,
                        null,
                        null,                        true
                    );
                    setTimeout(removeOverlay, 2500);
                }
            },
            onerror: function(error) {
                console.error('URL validation failed:', error);
                updateOverlay(
                    'Download failed', 
                    'Cannot access download URL - may be expired or invalid',
                    null,
                    null,                    true
                );
                setTimeout(removeOverlay, 2500);
            }
        });
    }
      function fetchAndDownload(url, filename, downloadStartTime) {
        updateOverlay('Starting download', 'Connecting to server...', '0 B', '0 B/s');
        
        console.log('=== FETCH AND DOWNLOAD ===');
        console.log('URL:', url);
        console.log('Filename:', filename);
        console.log('Method: GM.xmlHttpRequest with responseType blob');
        console.log('Start time:', new Date(downloadStartTime).toISOString());
        console.log('==========================');
          let totalSize = 0;
        let downloadedSize = 0;
        let lastUpdateTime = 0;
        const UPDATE_INTERVAL = 250;
        
        GM.xmlHttpRequest({
            method: 'GET',
            url: url,
            responseType: 'blob',
            headers: {
                "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
                "Referer": "https://iframe.y2meta-uk.com/",
                "Accept": "*/*"
            },            onprogress: function(progressEvent) {
                const currentTime = Date.now();
                const elapsed = (currentTime - downloadStartTime) / 1000;
                
                const shouldUpdate = (currentTime - lastUpdateTime) >= UPDATE_INTERVAL || 
                                   (progressEvent.lengthComputable && progressEvent.loaded === progressEvent.total);
                
                if (progressEvent.lengthComputable) {
                    totalSize = progressEvent.total;
                    downloadedSize = progressEvent.loaded;
                    
                    const percentage = Math.round((downloadedSize / totalSize) * 100);
                    const speed = elapsed > 0 ? downloadedSize / elapsed : 0;
                    
                    if (shouldUpdate) {
                        const sizeText = `${formatBytes(downloadedSize)} / ${formatBytes(totalSize)}`;
                        const speedText = `${formatBytes(speed)}/s`;
                        const percentText = `${percentage}%`;
                        
                        updateOverlay(
                            `Downloading ${percentText}`, 
                            `${filename || 'video.mp4'}`,
                            sizeText,
                            speedText
                        );
                        
                        lastUpdateTime = currentTime;
                    }
                    
                    if ((currentTime - lastUpdateTime) >= 1000 || percentage === 100) {
                        console.log(`[${elapsed.toFixed(1)}s] Progress: ${percentage}% | Downloaded: ${formatBytes(downloadedSize)}/${formatBytes(totalSize)} | Speed: ${formatBytes(speed)}/s`);
                    }
                } else {
                    downloadedSize = progressEvent.loaded || 0;
                    const speed = elapsed > 0 ? downloadedSize / elapsed : 0;
                    
                    if (shouldUpdate) {
                        const sizeText = `${formatBytes(downloadedSize)}`;
                        const speedText = `${formatBytes(speed)}/s`;
                        const timeText = `${elapsed.toFixed(1)}s`;
                        
                        updateOverlay(
                            `Downloading...`, 
                            `${filename || 'video.mp4'} - ${timeText}`,
                            sizeText,
                            speedText
                        );
                        
                        lastUpdateTime = currentTime;
                    }
                    
                    if ((currentTime - lastUpdateTime) >= 1000) {
                        console.log(`[${elapsed.toFixed(1)}s] Downloaded: ${formatBytes(downloadedSize)} | Speed: ${formatBytes(speed)}/s`);
                    }
                }
            },
            onload: function(response) {
                console.log('Fetch completed. Response status:', response.status);
                console.log('Response type:', typeof response.response);
                console.log('Response size:', response.response?.size || 'unknown');
                
                if (response.status === 200 && response.response) {
                    updateOverlay('Creating download file', 'Converting to downloadable file...', formatBytes(response.response.size || 0), 'Processing');
                    
                    try {
                        const blob = response.response;
                        const blobUrl = URL.createObjectURL(blob);
                        
                        console.log('Blob created:', blob.size, 'bytes');
                        console.log('Blob URL:', blobUrl);
                        
                        const a = document.createElement('a');
                        a.style.display = 'none';
                        a.href = blobUrl;
                        a.download = filename || 'video.mp4';
                        
                        document.body.appendChild(a);
                        
                        a.click();
                        
                        setTimeout(() => {
                            document.body.removeChild(a);
                            URL.revokeObjectURL(blobUrl);
                        }, 1000);
                          updateOverlay(
                            'Download completed successfully!', 
                            `${filename || 'video.mp4'}`,
                            formatBytes(blob.size),
                            'Complete',
                            false,
                            true
                        );
                          console.log('✅ Download successful via blob method');
                        
                        setTimeout(() => {
                            removeOverlay();
                        }, 2500);
                        
                    } catch (blobError) {
                        console.error('Blob download failed:', blobError);
                        updateOverlay(
                            'Blob conversion failed', 
                            'Trying alternative download methods...',
                            null,
                            null,
                            true
                        );
                        
                        setTimeout(() => {
                            proceedWithDownload(url, filename, downloadStartTime);
                        }, 2000);
                    }
                    
                } else {
                    console.error('Fetch failed with status:', response.status);
                    updateOverlay(
                        'Data fetch failed', 
                        `Server returned status ${response.status}`,
                        null,
                        null,
                        true
                    );
                    
                    setTimeout(() => {
                        proceedWithDownload(url, filename, downloadStartTime);
                    }, 2000);
                }
            },            onerror: function(error) {
                console.error('GM.xmlHttpRequest fetch failed:', error);
                updateOverlay(
                    'Data fetch failed', 
                    'Trying native fetch method...',
                    null,
                    null,
                    true
                );
                
                setTimeout(() => {
                    nativeFetchDownload(url, filename, downloadStartTime);
                }, 2000);
            },
            ontimeout: function() {
                console.error('GM.xmlHttpRequest fetch timeout');
                updateOverlay(
                    'Download timeout', 
                    'Trying native fetch method...',
                    null,
                    null,
                    true
                );
                
                setTimeout(() => {
                    nativeFetchDownload(url, filename, downloadStartTime);
                }, 2000);
            }
        });
    }
      async function nativeFetchDownload(url, filename, downloadStartTime) {
        updateOverlay('Trying native fetch', 'Using browser fetch API...', 'Starting...', 'Native method');
        
        console.log('=== NATIVE FETCH DOWNLOAD ===');
        console.log('URL:', url);
        console.log('Filename:', filename);
        console.log('Method: Native fetch API with ReadableStream');
        console.log('=============================');
        
        try {
            const response = await fetch(url, {
                method: 'GET',
                headers: {
                    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
                    "Referer": "https://iframe.y2meta-uk.com/",
                    "Accept": "*/*"
                }
            });
            
            if (!response.ok) {
                throw new Error(`HTTP ${response.status}: ${response.statusText}`);
            }
            
            const contentLength = response.headers.get('content-length');
            const totalSize = contentLength ? parseInt(contentLength, 10) : 0;
            
            console.log('Native fetch response OK. Content-Length:', totalSize);
            
            if (response.body && totalSize > 0) {
                const reader = response.body.getReader();
                const chunks = [];
                let downloadedSize = 0;
                
                updateOverlay(
                    'Downloading with native fetch', 
                    `Total size: ${formatBytes(totalSize)}`,
                    '0%',
                    'Starting...'
                );
                
                while (true) {
                    const { done, value } = await reader.read();
                    
                    if (done) break;
                    
                    chunks.push(value);
                    downloadedSize += value.length;
                    
                    const percentage = (downloadedSize / totalSize) * 100;
                    const elapsed = (Date.now() - downloadStartTime) / 1000;
                    const speed = elapsed > 0 ? downloadedSize / elapsed : 0;
                    
                    const sizeText = `${formatBytes(downloadedSize)} / ${formatBytes(totalSize)}`;
                    const speedText = `${formatBytes(speed)}/s`;
                    
                    updateOverlay(
                        'Downloading with native fetch', 
                        `${Math.round(percentage)}% - ${filename || 'video.mp4'}`,
                        sizeText,
                        speedText
                    );
                    
                    console.log(`Native fetch progress: ${Math.round(percentage)}% | ${sizeText} | ${speedText}`);
                }
                
                const blob = new Blob(chunks);
                
                console.log('Native fetch blob created from chunks:', blob.size, 'bytes');
                
                const blobUrl = URL.createObjectURL(blob);
                const a = document.createElement('a');
                a.style.display = 'none';
                a.href = blobUrl;
                a.download = filename || 'video.mp4';
                
                document.body.appendChild(a);
                a.click();
                  setTimeout(() => {
                    document.body.removeChild(a);
                    URL.revokeObjectURL(blobUrl);
                }, 1000);
                
                updateOverlay(
                    'Native fetch download completed!', 
                    `${filename || 'video.mp4'}`,
                    formatBytes(blob.size),
                    'Complete',
                    false,
                    true
                );
                
            } else {
                updateOverlay('Downloading with native fetch', 'Size unknown, downloading...', 'Unknown size', 'Downloading...');
                
                const blob = await response.blob();
                
                console.log('Native fetch blob created (no progress):', blob.size, 'bytes');
                
                const blobUrl = URL.createObjectURL(blob);
                const a = document.createElement('a');
                a.style.display = 'none';
                a.href = blobUrl;
                a.download = filename || 'video.mp4';
                
                document.body.appendChild(a);
                a.click();
                  setTimeout(() => {
                    document.body.removeChild(a);
                    URL.revokeObjectURL(blobUrl);
                }, 1000);
                
                updateOverlay(
                    'Native fetch download completed!', 
                    `${filename || 'video.mp4'}`,
                    formatBytes(blob.size),
                    'Complete',
                    false,
                    true
                );
            }
              console.log('✅ Download successful via native fetch');
            
            setTimeout(() => {
                removeOverlay();
            }, 2500);
            
        } catch (fetchError) {
            console.error('Native fetch failed:', fetchError);
            updateOverlay(
                'Native fetch failed', 
                `Error: ${fetchError.message}`,
                null,
                null,
                true
            );
            
            setTimeout(() => {
                proceedWithDownload(url, filename, downloadStartTime);
            }, 2000);
        }
    }function proceedWithDownload(url, filename, downloadStartTime) {
        console.log('=== FALLBACK DOWNLOAD METHODS ===');
        console.log('GM_download (legacy):', typeof GM_download, GM_download);
        console.log('GM.download (new):', typeof GM?.download, GM?.download);
        console.log('Download URL:', url);
        console.log('Filename:', filename);
        console.log('==================================');
        
        updateOverlay('Opening download in new tab', 'Most reliable method for video downloads', null, null);
        
        try {
            const downloadWindow = window.open(url, '_blank', 'noopener,noreferrer');
            
            if (downloadWindow) {
                console.log('New tab opened successfully');                
                updateOverlay(
                    'Download opened in new tab', 
                    `${truncateTitle(filename || 'video.mp4')} - Check Downloads folder`,
                    'Via new tab',
                    'Browser handling',
                    false,
                    true
                );
                
                setTimeout(() => {
                    removeOverlay();
                }, 10000);
                
                return;
            } else {
                console.log('New tab blocked, trying GM_download');
                attemptGMDownload(url, filename, downloadStartTime);
            }
            
        } catch (error) {
            console.error('New tab method failed:', error);
            attemptGMDownload(url, filename, downloadStartTime);
        }
    }
    
    function attemptGMDownload(url, filename, downloadStartTime) {
        const gmDownloadAvailable = typeof GM_download !== 'undefined' && GM_download;
        const gmDownloadNewSyntax = typeof GM !== 'undefined' && GM.download;
        
        if (gmDownloadAvailable) {
            try {
                updateOverlay('Trying GM_download method', `File: ${truncateTitle(filename || 'video.mp4')}`, 'Initializing...', '-');
                
                console.log('Using GM_download (legacy API)');
                
                const downloadId = GM_download(url, filename || 'video.mp4', {
                    headers: {
                        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
                        "Referer": "https://iframe.y2meta-uk.com/",
                        "Accept": "*/*"
                    },
                    onprogress: function(progressEvent) {
                        console.log('Download progress (legacy):', progressEvent);
                        
                        if (progressEvent.lengthComputable) {
                            const totalSize = progressEvent.total;
                            const downloadedSize = progressEvent.loaded;
                            
                            const percentage = (downloadedSize / totalSize) * 100;
                            const elapsed = (Date.now() - downloadStartTime) / 1000;
                            const speed = downloadedSize / elapsed;
                            
                            const sizeText = `${formatBytes(downloadedSize)} / ${formatBytes(totalSize)}`;
                            const speedText = `${formatBytes(speed)}/s`;
                              updateOverlay(
                                'Downloading via GM_download', 
                                `${Math.round(percentage)}% - ${truncateTitle(filename || 'video.mp4')}`,
                                sizeText,
                                speedText
                            );
                        } else {
                            const elapsed = (Date.now() - downloadStartTime) / 1000;                            
                            updateOverlay(
                                'Downloading via GM_download', 
                                `${truncateTitle(filename || 'video.mp4')} - ${elapsed.toFixed(1)}s elapsed`,
                                'Size unknown',
                                'Progress...'
                            );
                        }
                    },                    
                    onload: function() {                        
                        console.log('Download completed successfully (legacy)');
                        updateOverlay(
                            'Download completed successfully', 
                            `${truncateTitle(filename || 'video.mp4')}`,
                            'Complete',
                            'Done',
                            false,
                            true
                        );
                        
                        setTimeout(() => {
                            removeOverlay();
                        }, 2500);
                    },
                    onerror: function(error) {
                        console.error('GM_download error (legacy):', error);
                        updateOverlay(
                            'GM_download failed', 
                            'Trying alternative download methods...',
                            null,
                            null,
                            true
                        );
                        
                        setTimeout(() => {
                            fallbackDownload(url, filename);
                        }, 2000);
                    }
                });
                
                console.log('Download ID (legacy):', downloadId);
                
                setTimeout(() => {
                    console.log('Checking if GM_download callbacks fired...');                    
                    updateOverlay(
                        'GM_download may have CORS issues', 
                        'Switching to fallback methods...',
                        null,
                        null,
                        true
                    );
                    
                    setTimeout(() => {
                        fallbackDownload(url, filename);
                    }, 2000);
                }, 2500);
                
            } catch (downloadError) {
                console.error('GM_download exception:', downloadError);
                tryNewGMDownload(url, filename, downloadStartTime);
            }
        } else if (gmDownloadNewSyntax) {
            tryNewGMDownload(url, filename, downloadStartTime);
        } else {
            console.log('No GM download APIs available, using fallback');
            fallbackDownload(url, filename);
        }
    }
      function tryNewGMDownload(url, filename, downloadStartTime) {
        try {
            updateOverlay('Trying GM.download (new API)', `File: ${filename || 'video.mp4'}`, 'Initializing...', '-');
            
            console.log('Using GM.download (new API)');
            
            GM.download(url, filename || 'video.mp4', {
                headers: {
                    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
                    "Referer": "https://iframe.y2meta-uk.com/",
                    "Accept": "*/*"
                },
                onprogress: function(progressEvent) {
                    console.log('Download progress (new):', progressEvent);
                    
                    if (progressEvent.lengthComputable) {
                        const totalSize = progressEvent.total;
                        const downloadedSize = progressEvent.loaded;
                        
                        const percentage = (downloadedSize / totalSize) * 100;
                        const elapsed = (Date.now() - downloadStartTime) / 1000;
                        const speed = downloadedSize / elapsed;
                        
                        const sizeText = `${formatBytes(downloadedSize)} / ${formatBytes(totalSize)}`;
                        const speedText = `${formatBytes(speed)}/s`;
                        
                        updateOverlay(
                            'Downloading via GM.download', 
                            `${Math.round(percentage)}% - ${filename || 'video.mp4'}`,
                            sizeText,
                            speedText
                        );
                    } else {
                        const elapsed = (Date.now() - downloadStartTime) / 1000;
                        updateOverlay(
                            'Downloading via GM.download', 
                            `${filename || 'video.mp4'} - ${elapsed.toFixed(1)}s elapsed`,
                            'Size unknown',
                            'Progress...'
                        );
                    }
                },                
                onload: function() {                    
                    console.log('Download completed successfully (new)');
                    updateOverlay(
                        'Download completed successfully', 
                        `${filename || 'video.mp4'}`,
                        'Complete',
                        'Done',
                        false,
                        true
                    );
                    
                    setTimeout(() => {
                        removeOverlay();
                    }, 2500);
                },
                onerror: function(error) {
                    console.error('GM.download error (new):', error);
                    updateOverlay(
                        'GM.download failed', 
                        'Trying alternative download methods...',
                        null,
                        null,
                        true
                    );
                    
                    setTimeout(() => {
                        fallbackDownload(url, filename);
                    }, 2000);
                }
            }).then(downloadId => {
                console.log('Download ID (new):', downloadId);
                setTimeout(() => {
                    console.log('Checking if GM.download callbacks fired...');
                    updateOverlay(
                        'GM.download may have CORS issues', 
                        'Switching to fallback methods...',
                        null,
                        null,
                        true
                    );
                    
                    setTimeout(() => {
                        fallbackDownload(url, filename);
                    }, 2000);
                }, 2500);
                
            }).catch(error => {
                console.error('GM.download promise error:', error);
                fallbackDownload(url, filename);
            });
            
        } catch (downloadError) {
            console.error('GM.download exception:', downloadError);
            fallbackDownload(url, filename);
        }
    }function fallbackDownload(url, filename) {
        updateOverlay('Using direct download methods', 'Testing browser download capabilities...', null, null);
        
        console.log('=== FALLBACK DOWNLOAD ===');
        console.log('URL:', url);
        console.log('Filename:', filename);
        console.log('=========================');
        
        try {
            const a = document.createElement('a');
            a.style.display = 'none';
            a.href = url;
            a.download = filename || 'video.mp4';
            a.target = '_blank';
            a.rel = 'noopener noreferrer';
            
            document.body.appendChild(a);
            
            updateOverlay('Method 1: Force download link', 'Creating download trigger...', null, null);
            
            const clickEvent = new MouseEvent('click', {
                bubbles: true,
                cancelable: true,
                view: window
            });
            
            a.dispatchEvent(clickEvent);
            
            a.click();
            
            setTimeout(() => {
                document.body.removeChild(a);
                
                updateOverlay(
                    'Download link triggered', 
                    `${filename || 'video.mp4'} - Check Downloads folder`,
                    'Via download link',
                    'Browser handling',
                    false,
                    true
                );
                
                setTimeout(() => {
                    trySecondaryMethod(url, filename);
                }, 3000);
                
            }, 1000);
            
        } catch (error) {
            console.error('Direct link method failed:', error);
            trySecondaryMethod(url, filename);
        }
    }
    
    function trySecondaryMethod(url, filename) {
        updateOverlay('Method 2: Location redirect', 'Attempting direct navigation...', null, null);
        
        try {
            const downloadWindow = window.open('', '_blank');
            
            if (downloadWindow) {
                downloadWindow.location.href = url;
                  updateOverlay(
                    'Download redirected to new tab', 
                    `${filename || 'video.mp4'} - Check new tab`,
                    'Via location redirect',
                    'Tab navigation',
                    false,
                    true
                );
                
                setTimeout(() => {
                    tryFinalMethod(url, filename);
                }, 2500);
            } else {
                tryFinalMethod(url, filename);
            }
            
        } catch (error) {
            console.error('Secondary method failed:', error);
            tryFinalMethod(url, filename);
        }
    }
    
    function tryFinalMethod(url, filename) {
        updateOverlay('Method 3: Manual URL access', 'Preparing manual download option...', null, null);
        
        try {
            navigator.clipboard.writeText(url).then(() => {
                updateOverlay(
                    'URL copied to clipboard!', 
                    'Open new tab and paste (Ctrl+L, Ctrl+V, Enter)',
                    'Clipboard ready',
                    'Manual paste',
                    false,
                    true
                );
                
                console.log('=== MANUAL DOWNLOAD URL ===');
                console.log('URL copied to clipboard. Paste in new tab:');
                console.log(url);
                console.log('Filename:', filename);
                console.log('===========================');
                
            }).catch(() => {
                showConsoleMethod(url, filename);
            });
        } catch (error) {
            showConsoleMethod(url, filename);
        }
        setTimeout(removeOverlay, 10000);
    }
    
    function showConsoleMethod(url, filename) {
        console.log('=== MANUAL DOWNLOAD URL ===');
        console.log('Copy this URL and paste in new browser tab:');
        console.log(url);
        console.log('Filename:', filename);
        console.log('===========================');
        
        updateOverlay(
            'Check browser console (F12)', 
            'URL available in console for manual copy',
            'Console method',
            'Manual copy/paste',
            false,
            false
        );
    }

    function createDownloadDialog() {
        const dialog = document.createElement('div');        
        dialog.className = 'ytddl-dialog';        
        const title = document.createElement('h3');
        title.textContent = '';

        const formatSelector = document.createElement('div');
        formatSelector.className = 'format-selector';        
        const videoBtn = document.createElement('button');
        videoBtn.className = `format-button ${lastSelectedFormat === 'video' ? 'selected' : ''}`;
        videoBtn.setAttribute('data-format', 'video');
        videoBtn.textContent = 'VIDEO (MP4)';

        const audioBtn = document.createElement('button');
        audioBtn.className = `format-button ${lastSelectedFormat === 'audio' ? 'selected' : ''}`;
        audioBtn.setAttribute('data-format', 'audio');
        audioBtn.textContent = 'AUDIO (MP3)';

        formatSelector.appendChild(videoBtn);
        formatSelector.appendChild(audioBtn);

        const qualityContainer = document.createElement('div');
        qualityContainer.id = 'quality-container';        
        const videoQualities = document.createElement('div');
        videoQualities.className = 'quality-options';
        videoQualities.id = 'video-qualities';
        videoQualities.style.display = lastSelectedFormat === 'video' ? 'grid' : 'none';        
        ['144p', '240p', '360p', '480p', '720p', '1080p'].forEach((quality, index) => {
            const option = document.createElement('div');
            option.className = 'quality-option';

            const input = document.createElement('input');
            input.type = 'radio';
            input.id = `quality-${index}`;
            input.name = 'quality';
            input.value = quality.replace('p', '');

            const label = document.createElement('label');
            label.setAttribute('for', `quality-${index}`);
            label.textContent = quality;
            label.style.fontSize = '14px';
            label.style.cursor = 'pointer';

            option.appendChild(input);
            option.appendChild(label);
            videoQualities.appendChild(option);

            option.addEventListener('click', function() {
                input.checked = true;
                GM_setValue('lastSelectedVideoQuality', input.value);
                lastSelectedVideoQuality = input.value;
            });
        });

        const defaultQuality = videoQualities.querySelector(`input[value="${lastSelectedVideoQuality}"]`);
        if (defaultQuality) {
            defaultQuality.checked = true;
        }        
        const audioQualities = document.createElement('div');
        audioQualities.className = 'quality-options';
        audioQualities.id = 'audio-qualities';
        audioQualities.style.display = lastSelectedFormat === 'audio' ? 'grid' : 'none';        
        ['128', '256', '320'].forEach((bitrate, index) => {
            const option = document.createElement('div');
            option.className = 'quality-option';

            const input = document.createElement('input');
            input.type = 'radio';
            input.id = `bitrate-${index}`;
            input.name = 'bitrate';
            input.value = bitrate;

            const label = document.createElement('label');
            label.setAttribute('for', `bitrate-${index}`);
            label.textContent = `${bitrate} kbps`;
            label.style.fontSize = '14px';
            label.style.cursor = 'pointer';

            option.appendChild(input);
            option.appendChild(label);
            audioQualities.appendChild(option);

            option.addEventListener('click', function() {
                input.checked = true;
                GM_setValue('lastSelectedAudioBitrate', input.value);
                lastSelectedAudioBitrate = input.value;
            });
        });

        const defaultBitrate = audioQualities.querySelector(`input[value="${lastSelectedAudioBitrate}"]`);
        if (defaultBitrate) {
            defaultBitrate.checked = true;
        }

        qualityContainer.appendChild(videoQualities);
        qualityContainer.appendChild(audioQualities);

        const downloadStatus = document.createElement('div');
        downloadStatus.className = 'download-status';
        downloadStatus.id = 'download-status';

        const buttonContainer = document.createElement('div');
        buttonContainer.className = 'button-container';

        const cancelButton = document.createElement('button');
        cancelButton.className = 'ytddl-button cancel';
        cancelButton.textContent = 'Cancel';

        const downloadButton = document.createElement('button');
        downloadButton.className = 'ytddl-button';
        downloadButton.textContent = 'Download';

        buttonContainer.appendChild(cancelButton);
        buttonContainer.appendChild(downloadButton);

        dialog.appendChild(title);
        dialog.appendChild(formatSelector);
        dialog.appendChild(qualityContainer);
        dialog.appendChild(downloadStatus);
        dialog.appendChild(buttonContainer);

        formatSelector.addEventListener('click', (e) => {
            if (e.target.classList.contains('format-button')) {
                formatSelector.querySelectorAll('.format-button').forEach(btn => {
                    btn.classList.remove('selected');
                });
                e.target.classList.add('selected');                
                const format = e.target.getAttribute('data-format');
                if (format === 'video') {
                    videoQualities.style.display = 'grid';
                    audioQualities.style.display = 'none';
                    lastSelectedFormat = 'video';
                    GM_setValue('lastSelectedFormat', 'video');
                } else {
                    videoQualities.style.display = 'none';
                    audioQualities.style.display = 'grid';
                    lastSelectedFormat = 'audio';
                    GM_setValue('lastSelectedFormat', 'audio');
                }
            }
        });

        const backdrop = document.createElement('div');
        backdrop.className = 'ytddl-backdrop';

        return { dialog, backdrop, cancelButton, downloadButton };
    }

    function closeDialog(dialog, backdrop) {
        if (dialog && dialog.parentNode) {
            dialog.parentNode.removeChild(dialog);
        }
        if (backdrop && backdrop.parentNode) {
            backdrop.parentNode.removeChild(backdrop);
        }
    }

    function extractVideoId(url) {
        const urlObj = new URL(url);
        const searchParams = new URLSearchParams(urlObj.search);
        return searchParams.get('v');
    }    async function downloadWithMP3YouTube(videoUrl, format, quality) {
        const statusElement = document.getElementById('download-status');
        
        createOverlay();
        
        if (statusElement) {
            statusElement.style.display = 'block';
            statusElement.textContent = 'Getting API key...';
        }

        try {
            updateOverlay('Getting API key', 'Connecting to MP3YouTube API...');

            const keyResponse = await new Promise((resolve, reject) => {
                GM.xmlHttpRequest({
                    method: 'GET',
                    url: API_KEY_URL,
                    headers: REQUEST_HEADERS,
                    onload: resolve,
                    onerror: reject,
                    ontimeout: reject
                });
            });

            const keyData = JSON.parse(keyResponse.responseText);
            if (!keyData || !keyData.key) {
                throw new Error('Failed to get API key');
            }

            const key = keyData.key;
            
            updateOverlay('Processing request', `${format} (${format === 'video' ? quality + 'p' : quality + ' kbps'})`);
            
            if (statusElement) {
                statusElement.textContent = 'Processing download...';
            }

            let payload;
            if (format === 'video') {
                payload = {
                    "link": videoUrl,
                    "format": "mp4",
                    "audioBitrate": "128",
                    "videoQuality": quality,
                    "filenameStyle": "pretty",
                    "vCodec": "h264"
                };
            } else {
                payload = {
                    "link": videoUrl,
                    "format": "mp3",
                    "audioBitrate": quality,
                    "filenameStyle": "pretty"
                };
            }

            const customHeaders = {
                ...REQUEST_HEADERS,
                "key": key
            };

            updateOverlay('Converting media', 'Processing video/audio conversion...');

            const downloadResponse = await new Promise((resolve, reject) => {
                GM.xmlHttpRequest({
                    method: 'POST',
                    url: API_CONVERT_URL,
                    headers: customHeaders,
                    data: JSON.stringify(payload),
                    onload: resolve,
                    onerror: reject,
                    ontimeout: reject
                });
            });

            const downloadInfo = JSON.parse(downloadResponse.responseText);
              if (downloadInfo.url) {
                updateOverlay('Starting download', `File: ${truncateTitle(downloadInfo.filename || `video.${format === 'video' ? 'mp4' : 'mp3'}`)}`);
                
                if (statusElement) {
                    statusElement.textContent = 'Starting download...';
                }
                
                triggerDirectDownload(downloadInfo.url, downloadInfo.filename);
                
                return downloadInfo;
            } else {
                throw new Error('No download URL received from API');
            }} catch (error) {
            updateOverlay('Download failed', `Error: ${error.message}`, null, null, true);
            setTimeout(() => {
                removeOverlay();
            }, 4000);
            
            throw error;
        }
    }

    function createDownloadButton() {
        const downloadButton = document.createElement('div');
        downloadButton.className = 'ytddl-download-btn';
        
        const svgNS = "http://www.w3.org/2000/svg";
        const svg = document.createElementNS(svgNS, "svg");
        svg.setAttribute("viewBox", "0 0 512 512");
        
        const path = document.createElementNS(svgNS, "path");
        path.setAttribute("d", "M256 464c114.9 0 208-93.1 208-208c0-13.3 10.7-24 24-24s24 10.7 24 24c0 141.4-114.6 256-256 256S0 397.4 0 256c0-13.3 10.7-24 24-24s24 10.7 24 24c0 114.9 93.1 208 208 208zM377.6 232.3l-104 112c-4.5 4.9-10.9 7.7-17.6 7.7s-13-2.8-17.6-7.7l-104-112c-9-9.7-8.5-24.9 1.3-33.9s24.9-8.5 33.9 1.3L232 266.9 232 24c0-13.3 10.7-24 24-24s24 10.7 24 24l0 242.9 62.4-67.2c9-9.7 24.2-10.3 33.9-1.3s10.3 24.2 1.3 33.9z");
        
        svg.appendChild(path);
        downloadButton.appendChild(svg);
        
        downloadButton.addEventListener('click', function() {
            showDownloadDialog();
        });
        
        return downloadButton;
    }

    function showDownloadDialog() {
        const videoUrl = window.location.href;
        const videoId = extractVideoId(videoUrl);
        
        if (!videoId) {
            alert('Could not extract video ID from URL');
            return;
        }

        const { dialog, backdrop, cancelButton, downloadButton } = createDownloadDialog();
        
        document.body.appendChild(backdrop);
        document.body.appendChild(dialog);

        backdrop.addEventListener('click', () => {
            closeDialog(dialog, backdrop);
        });

        cancelButton.addEventListener('click', () => {
            closeDialog(dialog, backdrop);
        });        downloadButton.addEventListener('click', async () => {
            const selectedFormat = dialog.querySelector('.format-button.selected').getAttribute('data-format');
            let quality;
            
            if (selectedFormat === 'video') {
                const selectedQuality = dialog.querySelector('input[name="quality"]:checked');
                if (!selectedQuality) {
                    alert('Please select a video quality');
                    return;
                }
                quality = selectedQuality.value;
            } else {
                const selectedBitrate = dialog.querySelector('input[name="bitrate"]:checked');
                if (!selectedBitrate) {
                    alert('Please select an audio bitrate');
                    return;
                }
                quality = selectedBitrate.value;
            }

            GM_setValue('lastSelectedFormat', selectedFormat);
            
            closeDialog(dialog, backdrop);
              try {
                await downloadWithMP3YouTube(videoUrl, selectedFormat, quality);            
            } catch (error) {                
                console.error('Download error:', error);
                updateOverlay('Download Failed', `Error: ${error.message}`, null, null, true);
                setTimeout(removeOverlay, 2500);
            }
        });
    }
    
    function insertDownloadButton() {
        const targetSelector = '#owner';
        const target = document.querySelector(targetSelector);
        
        if (target && !document.querySelector('.ytddl-download-btn')) {
            const downloadButton = createDownloadButton();
            target.appendChild(downloadButton);
        }
    }
    const observer = new MutationObserver(() => {
        if (window.location.pathname.includes('/watch')) {
            insertDownloadButton();
        }
    });
    
    observer.observe(document.body, { childList: true, subtree: true });
    insertDownloadButton();
    
    window.addEventListener('yt-navigate-finish', () => {
        insertDownloadButton();
    });
})();