Youtube Video Downloader 2025

Download Youtube videos in various formats. Download multiple videos at once.

// ==UserScript==
// @name         Youtube Video Downloader 2025
// @namespace    http://tampermonkey.net/
// @author       fb
// @version      1.3.1
// @description  Download Youtube videos in various formats. Download multiple videos at once.
// @match        https://www.youtube.com/*
// @grant        GM_xmlhttpRequest
// @grant        GM_setValue
// @grant        GM_getValue
// @connect      p.oceansaver.in
// @license      GPL-3.0-or-later
// @run-at       document-end
// ==/UserScript==

(function() {
    'use strict';

    const STORAGE_FORMAT = 'selectedFormat';
    const STORAGE_DOWNLOADS = 'ytDownloads';
    const UI_WRAPPER_ID = 'yt-downloader-wrapper';
    const FORMAT_BUTTON_ID = 'yt-downloader-format-button';
    const FORMAT_POPUP_ID = 'yt-downloader-format-popup';
    const COMBINED_BUTTON_ID = 'yt-downloader-combined-button';
    const DOWNLOAD_ACTION_ID = 'yt-downloader-action-part';
    const DROPDOWN_ACTION_ID = 'yt-downloader-dropdown-part';
    const DOWNLOAD_POPUP_ID = 'download-dropdown-popup';
    const POLL_INTERVALS = {};

    // Format definitions
    const FORMAT_GROUPS = [
        { label: 'Audio', options: [['mp3','MP3'],['m4a','M4A'],['webm','WEBM'],['aac','AAC'],['flac','FLAC'],['opus','OPUS'],['ogg','OGG'],['wav','WAV']] },
        { label: 'Video', options: [['360','MP4 (360p)'],['480','MP4 (480p)'],['720','MP4 (720p)'],['1080','MP4 (1080p)'],['1440','MP4 (1440p)'],['4k','WEBM (4K)']] }
    ];
    const DEFAULT_FORMAT = '1080';

    function getFormatText(value) {
        for (const group of FORMAT_GROUPS) {
            for (const [val, text] of group.options) {
                if (val === value) return text;
            }
        }
        return 'Select Format';
    }

    function isDarkTheme() {
        return document.documentElement.hasAttribute('dark');
    }

    function isYouTubeLiveStream() {
        const ypr = window.ytInitialPlayerResponse || {};
        return !!(
            ypr.videoDetails?.isLiveContent === true ||
            ypr.microformat?.playerMicroformatRenderer?.liveBroadcastDetails ||
            document.querySelector('meta[itemprop="isLiveBroadcast"][content="True"]') ||
            document.querySelector('.ytp-live')
        );
    }

    function checkPageAndInjectUI() {
        const existing = document.getElementById(UI_WRAPPER_ID);
        const container = document.querySelector('#end');

        if (container && !existing) injectUI(container);
        else if (!container && existing) removeUI();

        updateUIState();
    }

    document.addEventListener('yt-navigate-finish', checkPageAndInjectUI);
    window.addEventListener('load', checkPageAndInjectUI);

    function updateUIState() {
        const wrapper = document.getElementById(UI_WRAPPER_ID);
        if (!wrapper) return;
        const isWatchOrShorts = window.location.pathname === '/watch' || window.location.pathname.startsWith('/shorts/');
        const disabled = !isWatchOrShorts || isYouTubeLiveStream();

        const formatButton = wrapper.querySelector(`#${FORMAT_BUTTON_ID}`);
        const downloadActionPart = wrapper.querySelector(`#${DOWNLOAD_ACTION_ID}`);

        if(formatButton) {
             formatButton.disabled = disabled;
             formatButton.style.opacity = disabled ? 0.6 : 1;
             formatButton.style.cursor = disabled ? 'not-allowed' : 'var(--btn-cursor)';
        }
        if(downloadActionPart) {
            downloadActionPart.classList.toggle('disabled', disabled);
            downloadActionPart.style.pointerEvents = disabled ? 'none' : 'auto';
        }
    }

    function injectUI(container) {
        if (document.getElementById(UI_WRAPPER_ID)) return;
        const wrapper = document.createElement('div');
        wrapper.id = UI_WRAPPER_ID;
        wrapper.style.display = 'flex';
        wrapper.style.alignItems = 'center';
        wrapper.style.marginRight = '10px';
        wrapper.style.position = 'relative';

        // --- Format Button ---
        const formatButton = document.createElement('button');
        formatButton.id = FORMAT_BUTTON_ID;
        formatButton.style.marginRight = '8px';
        const savedFormat = GM_getValue(STORAGE_FORMAT, DEFAULT_FORMAT);
        formatButton.dataset.value = savedFormat;
        formatButton.textContent = getFormatText(savedFormat);
        formatButton.addEventListener('click', toggleFormatPopup);
        wrapper.appendChild(formatButton);

        // --- Combined Download/Dropdown Button ---
        const combinedBtn = document.createElement('div');
        combinedBtn.id = COMBINED_BUTTON_ID;
        combinedBtn.style.display = 'inline-flex';
        combinedBtn.style.alignItems = 'stretch';
        combinedBtn.style.height = '36px';
        combinedBtn.style.borderRadius = 'var(--btn-radius)';
        combinedBtn.style.backgroundColor = 'var(--btn-bg)';
        combinedBtn.style.cursor = 'default';
        combinedBtn.style.position = 'relative';

        
        const downloadPart = document.createElement('div');
        downloadPart.id = DOWNLOAD_ACTION_ID;
        downloadPart.title = 'Download Video/Audio';
        downloadPart.style.display = 'inline-flex';
        downloadPart.style.alignItems = 'center';
        downloadPart.style.padding = '0 12px 0 8px';
        downloadPart.style.cursor = 'var(--btn-cursor)';
        downloadPart.style.transition = 'background-color .2s ease';
        downloadPart.style.borderRadius = 'var(--btn-radius) 0 0 var(--btn-radius)';

        // Create SVG element programmatically
        const downloadSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
        downloadSvg.setAttribute('viewBox', '0 0 24 24');
        downloadSvg.setAttribute('width', '24');
        downloadSvg.setAttribute('height', '24');
        downloadSvg.style.marginRight = '6px';
        downloadSvg.style.fill = 'none';
        downloadSvg.style.stroke = 'currentColor';
        downloadSvg.style.strokeWidth = '1.5';
        downloadSvg.style.strokeLinecap = 'round';
        downloadSvg.style.strokeLinejoin = 'round';

        const path1 = document.createElementNS('http://www.w3.org/2000/svg', 'path');
        path1.setAttribute('d', 'M12 4v12');
        const path2 = document.createElementNS('http://www.w3.org/2000/svg', 'path');
        path2.setAttribute('d', 'M8 12l4 4 4-4');
        const path3 = document.createElementNS('http://www.w3.org/2000/svg', 'path');
        path3.setAttribute('d', 'M4 18h16');

        downloadSvg.appendChild(path1);
        downloadSvg.appendChild(path2);
        downloadSvg.appendChild(path3);

        // Create span element programmatically
        const downloadSpan = document.createElement('span');
        downloadSpan.textContent = 'Download';

        // Append elements
        downloadPart.appendChild(downloadSvg);
        downloadPart.appendChild(downloadSpan);

        downloadPart.addEventListener('click', startDownload);
        downloadPart.addEventListener('mouseenter', () => { if (!downloadPart.classList.contains('disabled')) downloadPart.style.backgroundColor = 'var(--btn-hover-bg)'; });
        downloadPart.addEventListener('mouseleave', () => { downloadPart.style.backgroundColor = 'transparent'; });


        const separator = document.createElement('div');
        separator.style.width = '1px';
        separator.style.backgroundColor = 'var(--separator-color)';
        separator.style.height = '20px';
        separator.style.alignSelf = 'center';

        const dropdownPart = document.createElement('div');
        dropdownPart.id = DROPDOWN_ACTION_ID;
        dropdownPart.dataset.count = '0';
        dropdownPart.title = 'Show active downloads';
        dropdownPart.style.display = 'inline-flex';
        dropdownPart.style.alignItems = 'center';
        dropdownPart.style.justifyContent = 'center';
        dropdownPart.style.padding = '0 10px';
        dropdownPart.style.cursor = 'var(--btn-cursor)';
        dropdownPart.style.position = 'relative';
        dropdownPart.style.transition = 'background-color .2s ease';
        dropdownPart.style.borderRadius = '0 var(--btn-radius) var(--btn-radius) 0';
        const darkTheme = isDarkTheme();

        // Create SVG element programmatically
        const dropdownSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
        dropdownSvg.setAttribute('width', '10');
        dropdownSvg.setAttribute('height', '7');
        dropdownSvg.setAttribute('viewBox', '0 0 10 7');
        dropdownSvg.style.fill = darkTheme ? '#fff' : '#000';

        const dropdownPath = document.createElementNS('http://www.w3.org/2000/svg', 'path');
        dropdownPath.setAttribute('d', 'M0 0l5 7 5-7z');
        dropdownSvg.appendChild(dropdownPath);

        // Append SVG
        dropdownPart.appendChild(dropdownSvg);

        dropdownPart.addEventListener('click', toggleDownloadPopup);
        dropdownPart.addEventListener('mouseenter', () => { dropdownPart.style.backgroundColor = 'var(--btn-hover-bg)'; });
        dropdownPart.addEventListener('mouseleave', () => { dropdownPart.style.backgroundColor = 'transparent'; });

        combinedBtn.appendChild(downloadPart);
        combinedBtn.appendChild(separator);
        combinedBtn.appendChild(dropdownPart);
        wrapper.appendChild(combinedBtn);

        container.insertAdjacentElement('afterbegin', wrapper);

        const style = document.createElement('style');

        style.textContent = `
:root {
    --btn-bg: ${darkTheme ? "#272727" : "#f2f2f2"};
    --btn-hover-bg: ${darkTheme ? "#3f3f3f" : "#e5e5e5"};
    --btn-color: ${darkTheme ? "#fff" : "#000"};
    --btn-radius: 18px;
    --btn-padding: 0 12px;
    --btn-font: 500 14px/36px "Roboto", "Arial", sans-serif;
    --btn-cursor: pointer;
    --progress-bg: ${darkTheme ? "#3f3f3f" : "#e5e5e5"};
    --progress-fill-color: #2196F3;
    --progress-text-color: ${darkTheme ? "#fff" : "#000"};
    --popup-bg: ${darkTheme ? "#212121" : "#fff"};
    --popup-border: ${darkTheme ? "#444" : "#ccc"};
    --popup-text: ${darkTheme ? "#fff" : "#030303"};
    --badge-bg: #cc0000;
    --badge-text: #fff;
    --separator-color: ${darkTheme ? 'rgba(255,255,255,0.2)' : 'rgba(0,0,0,0.1)'};
    --popup-radius: 6px;
    --popup-shadow: 0 4px 12px rgba(0,0,0,0.15);
}

/* Format Button Styling */
#${FORMAT_BUTTON_ID} {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: var(--btn-color);
    background-color: var(--btn-bg);
    border: none;
    border-radius: var(--btn-radius);
    padding: 0 12px;
    padding-right: 30px;
    white-space: nowrap;
    text-transform: none;
    font: var(--btn-font);
    cursor: var(--btn-cursor);
    transition: background-color .2s ease;
    height: 36px;
    width: 130px;
    box-sizing: border-box;
    position: relative;
    box-shadow: none;
}
#${FORMAT_BUTTON_ID}:disabled {
    cursor: not-allowed;
    opacity: 0.6;
}
#${FORMAT_BUTTON_ID}:hover:not(:disabled) {
    background-color: var(--btn-hover-bg);
}
/* Dropdown Arrow for Format Button */
#${FORMAT_BUTTON_ID}::after {
    content: '';
    position: absolute;
    right: 12px; /* Position arrow within the padding */
    top: 50%;
    transform: translateY(-50%);
    width: 0;
    height: 0;
    border-left: 5px solid transparent;
    border-right: 5px solid transparent;
    border-top: 7px solid var(--btn-color);
}

/* Combined Button Styling */
#${COMBINED_BUTTON_ID} {
    color: var(--btn-color);
    font: var(--btn-font);
    line-height: 36px;
}

#${DOWNLOAD_ACTION_ID}, #${DROPDOWN_ACTION_ID} {
    background-color: transparent;
}

#${DOWNLOAD_ACTION_ID} {
    color: inherit;
}
#${DOWNLOAD_ACTION_ID} svg {
    stroke: currentColor;
}
#${DOWNLOAD_ACTION_ID}.disabled {
    opacity: 0.6;
    cursor: not-allowed;
}
#${DOWNLOAD_ACTION_ID}.disabled:hover {
    background-color: transparent !important;
}

#${DROPDOWN_ACTION_ID} {
    color: inherit;
}
#${DROPDOWN_ACTION_ID} svg {
    fill: currentColor;
}

#${DROPDOWN_ACTION_ID}::after {
    content: attr(data-count);
    position: absolute;
    top: 2px;
    right: -8px;
    background-color: var(--badge-bg);
    color: var(--badge-text);
    border-radius: 50%;
    min-width: 16px;
    height: 16px;
    padding: 0 3px;
    font-size: 10px;
    line-height: 16px;
    text-align: center;
    font-weight: bold;
    display: none;
    font-family: "Roboto", "Arial", sans-serif;
    box-sizing: border-box;
    z-index: 2;
}
#${DROPDOWN_ACTION_ID}[data-count]:not([data-count="0"])::after {
    display: inline-block;
}

/* General Popup Styling */
#${FORMAT_POPUP_ID}, #${DOWNLOAD_POPUP_ID} {
    position: absolute;
    background: var(--popup-bg);
    color: var(--popup-text);
    border: 1px solid var(--popup-border);
    border-radius: var(--popup-radius);
    box-shadow: var(--popup-shadow);
    padding: 10px;
    z-index: 10000;
    max-height: 350px;
    overflow-y: auto;
}

/* Format Popup Specifics */
#${FORMAT_POPUP_ID} {
    width: 200px;
}
.format-group-label {
    font-weight: bold;
    font-size: 12px;
    color: ${darkTheme ? '#aaa' : '#555'};
    margin-top: 8px;
    margin-bottom: 4px;
    padding-left: 5px;
    text-transform: uppercase;
}
.format-group-label:first-child {
    margin-top: 0;
}
.format-item {
    display: block;
    width: 100%;
    padding: 6px 10px;
    font-size: 14px;
    cursor: pointer;
    border-radius: 4px;
    box-sizing: border-box;
    text-align: left;
    background: none;
    border: none;
    color: inherit;
}
.format-item:hover {
    background-color: var(--btn-hover-bg);
}
.format-item.selected {
    font-weight: bold;
    background-color: rgba(0, 100, 255, 0.1);
}


/* Download Popup Specifics */
#${DOWNLOAD_POPUP_ID} {
    width: 280px;
}
.download-item {
    margin-bottom: 12px;
    padding-bottom: 8px;
    border-bottom: 1px solid var(--popup-border);
}
.download-item:last-child {
    margin-bottom: 0;
    border-bottom: none;
}
.progress-bar {
    width: 100%;
    height: 18px;
    background-color: var(--progress-bg);
    border-radius: 9px;
    overflow: hidden;
    position: relative;
    margin-top: 4px;
}
.progress-fill {
    height: 100%;
    width: 0%;
    background-color: var(--progress-fill-color);
    transition: width 0.3s ease-in-out;
    display: flex;
    align-items: center;
    justify-content: center;
}
.progress-text {
    position: absolute; top: 0; left: 0; right: 0; bottom: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--progress-text-color);
    font: var(--btn-font);
    font-size: 11px;
    line-height: 18px;
    white-space: nowrap;
    z-index: 1;
}
.download-item-title {
    font-size: 13px;
    font-weight: 500;
    margin-bottom: 2px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    display: block;
}
.download-item-format {
    font-size: 11px;
    color: ${darkTheme ? '#aaa' : '#555'};
    display: block;
    margin-bottom: 4px;
}
.no-downloads-message {
    font-size: 15px;
    color: ${darkTheme ? '#aaa' : '#555'};
    text-align: center;
    padding: 10px 0;
}
        `;

        document.head.appendChild(style);

        resumeDownloads();
        updateDownloadCountBadge();
        updateUIState();
    }

    function removeUI() {
        const w = document.getElementById(UI_WRAPPER_ID);
        if (w) w.remove();
        // Remove both popups if they exist
        const formatPopup = document.getElementById(FORMAT_POPUP_ID);
        if (formatPopup) formatPopup.remove();
        const downloadPopup = document.getElementById(DOWNLOAD_POPUP_ID);
        if (downloadPopup) downloadPopup.remove();
    }

    function startDownload() {
        const downloadActionPart = document.getElementById(DOWNLOAD_ACTION_ID);
        if (downloadActionPart && downloadActionPart.classList.contains('disabled')) return;

        const formatButton = document.getElementById(FORMAT_BUTTON_ID);
        const fmt = formatButton.dataset.value;
        const formatText = formatButton.textContent;
        const videoUrl = encodeURIComponent(location.href);
        const initUrl = `https://p.oceansaver.in/ajax/download.php?format=${fmt}&url=${videoUrl}`;
        const id = Date.now().toString();
        const title = document.querySelector('h1.ytd-watch-metadata #video-title, h1.title.ytd-video-primary-info-renderer')?.textContent.trim() || 'YouTube Video';

        GM_xmlhttpRequest({
            method: 'GET', url: initUrl, responseType: 'json',
            onload(res) {
                const data = res.response;
                if (!data?.success) return alert('Failed to initialize');
                const downloads = GM_getValue(STORAGE_DOWNLOADS, []);
                downloads.push({ id, title, format: formatText, progress_url: data.progress_url, progress: 0, status: 'in_progress' });
                GM_setValue(STORAGE_DOWNLOADS, downloads);
                renderDownloadPopup(); // Update download popup if open
                updateDownloadCountBadge();
                pollProgress(id);
            },
            onerror() { alert('Network error'); }
        });
    }

    function pollProgress(id) {
        const downloads = GM_getValue(STORAGE_DOWNLOADS, []);
        const dl = downloads.find(d=>d.id===id);
        if (!dl || dl.status !== 'in_progress') return;

        if (POLL_INTERVALS[id]) clearInterval(POLL_INTERVALS[id]);

        const interval = setInterval(()=>{
            const currentDownloads = GM_getValue(STORAGE_DOWNLOADS, []);
            const currentDl = currentDownloads.find(d=>d.id===id);
            if (!currentDl || currentDl.status !== 'in_progress') {
                 console.log(`Stopping poll for ${id}, status changed.`);
                 clearInterval(interval);
                 delete POLL_INTERVALS[id];
                 renderDownloadPopup();
                 updateDownloadCountBadge();
                 return;
            }

            GM_xmlhttpRequest({ method:'GET', url: dl.progress_url, responseType:'json', onload(res){
                    const p = res.response;
                    const all = GM_getValue(STORAGE_DOWNLOADS, []);
                    const obj = all.find(x=>x.id===id);
                    if (!obj) {
                        clearInterval(interval); delete POLL_INTERVALS[id];
                        updateDownloadCountBadge();
                        return;
                    }
                    if (!p) {
                        console.warn(`Empty poll response for ${id}`);
                        return;
                    }
                    let statusChanged = false;
                    if (p.success) {
                        clearInterval(interval); delete POLL_INTERVALS[id];
                        obj.progress=100; obj.status='completed'; obj.download_url=p.download_url;
                        statusChanged = true;
                        GM_setValue(STORAGE_DOWNLOADS, all);
                        renderDownloadPopup();
                        triggerFileDownload(p.download_url);
                    } else if (p.error) {
                        clearInterval(interval); delete POLL_INTERVALS[id];
                        obj.status = 'error'; obj.errorMsg = p.error;
                        statusChanged = true;
                        GM_setValue(STORAGE_DOWNLOADS, all);
                        renderDownloadPopup();
                        console.error(`Download ${id} failed: ${p.error}`);
                    } else {
                        const percent = p.progress ? Math.min(Math.round(p.progress/10),100) : obj.progress;
                        if (obj.progress !== percent || obj.status !== 'in_progress') {
                            obj.progress = percent;
                            obj.status = 'in_progress';
                            GM_setValue(STORAGE_DOWNLOADS, all);
                            renderDownloadPopup();
                        }
                    }
                    if (statusChanged) {
                        updateDownloadCountBadge();
                    }
                },
                onerror(){
                    clearInterval(interval); delete POLL_INTERVALS[id];
                    const all = GM_getValue(STORAGE_DOWNLOADS, []);
                    const obj = all.find(x=>x.id===id);
                    if(obj) {
                        obj.status = 'error'; obj.errorMsg = 'Network error during polling';
                        GM_setValue(STORAGE_DOWNLOADS, all);
                        renderDownloadPopup();
                        updateDownloadCountBadge();
                    }
                    console.error(`Network error polling ${id}`);
                }
            });
        }, 2000);
        POLL_INTERVALS[id] = interval;
    }

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

    // --- Popup Toggle Functions ---

    function toggleFormatPopup() {
        let popup = document.getElementById(FORMAT_POPUP_ID);
        if (popup) { popup.remove(); return; }

        // Close download popup if open
        const downloadPopup = document.getElementById(DOWNLOAD_POPUP_ID);
        if (downloadPopup) downloadPopup.remove();

        const wrapper = document.getElementById(UI_WRAPPER_ID);
        const formatButton = document.getElementById(FORMAT_BUTTON_ID);
        if (!wrapper || !formatButton) return;

        popup = document.createElement('div');
        popup.id = FORMAT_POPUP_ID;
        wrapper.appendChild(popup);
        renderFormatPopup();

        // Position popup below format button
        const buttonRect = formatButton.getBoundingClientRect();
        const wrapperRect = wrapper.getBoundingClientRect();
        popup.style.top = (buttonRect.bottom - wrapperRect.top + 5) + 'px';
        popup.style.left = (buttonRect.left - wrapperRect.left) + 'px';

        setTimeout(() => {
            document.addEventListener('click', handleClickOutsideFormatPopup, { capture: true, once: true });
        }, 0);
    }

    function toggleDownloadPopup() {
        let popup = document.getElementById(DOWNLOAD_POPUP_ID);
        if (popup) { popup.remove(); return; }

        // Close format popup if open
        const formatPopup = document.getElementById(FORMAT_POPUP_ID);
        if (formatPopup) formatPopup.remove();

        const wrapper = document.getElementById(UI_WRAPPER_ID);
        const combinedButton = document.getElementById(COMBINED_BUTTON_ID);
        if (!wrapper || !combinedButton) return;

        popup = document.createElement('div');
        popup.id = DOWNLOAD_POPUP_ID;
        wrapper.appendChild(popup);
        renderDownloadPopup();

        // Position popup below combined button, aligned right
        const buttonRect = combinedButton.getBoundingClientRect();
        const wrapperRect = wrapper.getBoundingClientRect();
        popup.style.top = (buttonRect.bottom - wrapperRect.top + 5) + 'px';
        popup.style.right = (wrapperRect.right - buttonRect.right) + 'px';

        setTimeout(() => {
            document.addEventListener('click', handleClickOutsideDownloadPopup, { capture: true, once: true });
        }, 0);
    }

    // --- Popup Click Outside Handlers ---

    function handleClickOutsideFormatPopup(event) {
        const popup = document.getElementById(FORMAT_POPUP_ID);
        const button = document.getElementById(FORMAT_BUTTON_ID);
        if (popup && !popup.contains(event.target) && !button.contains(event.target)) {
            popup.remove();
        } else if (popup) {
            // Re-attach listener if click was inside popup or on button
            document.addEventListener('click', handleClickOutsideFormatPopup, { capture: true, once: true });
        }
    }

    function handleClickOutsideDownloadPopup(event) {
        const popup = document.getElementById(DOWNLOAD_POPUP_ID);
        const button = document.getElementById(DROPDOWN_ACTION_ID); // Check against the dropdown part
        if (popup && !popup.contains(event.target) && !button.contains(event.target)) {
            popup.remove();
        } else if (popup) {
            document.addEventListener('click', handleClickOutsideDownloadPopup, { capture: true, once: true });
        }
    }

    // --- Popup Render Functions ---

    function renderFormatPopup() {
        const popup = document.getElementById(FORMAT_POPUP_ID);
        if (!popup) return;
        popup.textContent = '';
        const currentFormat = GM_getValue(STORAGE_FORMAT, DEFAULT_FORMAT);

        FORMAT_GROUPS.forEach(group => {
            const groupLabel = document.createElement('div');
            groupLabel.className = 'format-group-label';
            groupLabel.textContent = group.label;
            popup.appendChild(groupLabel);

            group.options.forEach(([value, text]) => {
                const item = document.createElement('button');
                item.className = 'format-item';
                item.textContent = text;
                item.dataset.value = value;
                if (value === currentFormat) {
                    item.classList.add('selected');
                }
                item.onclick = () => {
                    GM_setValue(STORAGE_FORMAT, value);
                    const formatButton = document.getElementById(FORMAT_BUTTON_ID);
                    if (formatButton) {
                        formatButton.textContent = text;
                        formatButton.dataset.value = value;
                    }
                    popup.remove();
                };
                popup.appendChild(item);
            });
        });
    }


    function renderDownloadPopup() {
        const popup = document.getElementById(DOWNLOAD_POPUP_ID);
        if (!popup) return;
        popup.textContent = '';

        const downloads = GM_getValue(STORAGE_DOWNLOADS, [])
                         .filter(d => d.status === 'in_progress' || d.status === 'error')
                         .sort((a, b) => (b.id - a.id));

        if (!downloads.length) {
            const noDownloadsMsg = document.createElement('div');
            noDownloadsMsg.className = 'no-downloads-message';
            noDownloadsMsg.textContent = 'No active downloads.';
            popup.appendChild(noDownloadsMsg);
            return;
        }

        downloads.forEach(d => {
            const item = document.createElement('div');
            item.className = 'download-item';

            const titleDiv = document.createElement('div');
            titleDiv.className = 'download-item-title';
            titleDiv.textContent = d.title || `Download ${d.id}`;
            titleDiv.title = d.title || `Download ${d.id}`;
            item.appendChild(titleDiv);

            const formatDiv = document.createElement('div');
            formatDiv.className = 'download-item-format';
            formatDiv.textContent = d.format || 'Unknown Format';
            item.appendChild(formatDiv);

            if (d.status === 'in_progress') {
                const bar = document.createElement('div'); bar.className = 'progress-bar';
                const fill = document.createElement('div'); fill.className = 'progress-fill';
                fill.style.width = `${d.progress}%`;
                bar.appendChild(fill);
                const txt = document.createElement('div'); txt.className = 'progress-text';
                txt.textContent = `${d.progress}%`;
                bar.appendChild(txt);
                item.appendChild(bar);
            } else if (d.status === 'error') {
                const errorDiv = document.createElement('div');
                errorDiv.style.color = '#f44336'; errorDiv.style.fontSize = '12px';
                errorDiv.textContent = `Error: ${d.errorMsg || 'Unknown error'}`;
                item.appendChild(errorDiv);
            }
            popup.appendChild(item);
        });

         if (downloads.some(d => d.status === 'error')) {
            const clearButton = document.createElement('button');
            clearButton.textContent = 'Clear Errors';
            clearButton.style.marginTop = '10px';
            clearButton.style.fontSize = '12px';
            clearButton.style.padding = '4px 8px';
            clearButton.style.backgroundColor = 'var(--btn-bg)';
            clearButton.style.color = 'var(--btn-color)';
            clearButton.style.border = 'none';
            clearButton.style.borderRadius = '4px';
            clearButton.style.cursor = 'pointer';
            clearButton.onmouseover = () => clearButton.style.backgroundColor = 'var(--btn-hover-bg)';
            clearButton.onmouseout = () => clearButton.style.backgroundColor = 'var(--btn-bg)';

            clearButton.onclick = () => {
                const allDownloads = GM_getValue(STORAGE_DOWNLOADS, []);
                const keptDownloads = allDownloads.filter(dl => dl.status !== 'error');
                GM_setValue(STORAGE_DOWNLOADS, keptDownloads);
                renderDownloadPopup();
                updateDownloadCountBadge(); // Badge only shows 'in_progress', errors don't count
            };
            popup.appendChild(clearButton);
        }
    }

    function updateDownloadCountBadge() {
        const dropdownPart = document.getElementById(DROPDOWN_ACTION_ID);
        if (!dropdownPart) return;

        const downloads = GM_getValue(STORAGE_DOWNLOADS, []);
        const activeCount = downloads.filter(d => d.status === 'in_progress').length;

        dropdownPart.dataset.count = activeCount.toString();
    }

    function resumeDownloads() {
        const downloads = GM_getValue(STORAGE_DOWNLOADS, []).filter(d => d.status === 'in_progress');
        console.log(`Resuming ${downloads.length} downloads.`);
        downloads.forEach(d => {
            if (!POLL_INTERVALS[d.id]) {
                 pollProgress(d.id);
            }
        });
    }
})();