Instagram Downloader Pro Max

instagram downloader with checkbox selection as bulk of just download with download button next to save button for posts, pause button for stories

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Instagram Downloader Pro Max
// @namespace    igDownloaderProMax
// @version      1.3.60
// @description  instagram downloader with checkbox selection as bulk of just download with download button next to save button for posts, pause button for stories
// @author       Runterya
// @match        https://www.instagram.com/*
// @homepage     https://github.com/Runteryaa
// @icon         https://www.google.com/s2/favicons?sz=64&domain=instagram.com
// @license      MIT
// @grant        GM_xmlhttpRequest
// @grant        GM_setValue
// @grant        GM_getValue
// @require      https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js
// ==/UserScript==

(function() {
    'use strict';

    // --- CONFIG ---
    const CHECK_INTERVAL = 500;
    const API_URL = "https://backend1.tioo.eu.org/api/downloader/igdl";

    // --- MEMORY ---
    const selectedUrls = new Set();
    let isDownloading = false;

    // --- ICON ---
    const DOWNLOAD_ICON = `
    <svg aria-label="Download" class="x1lliihq x1n2onr6" color="currentColor" fill="none" height="24" width="24" role="img" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round">
        <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
        <polyline points="7 10 12 15 17 10"></polyline>
        <line x1="12" y1="15" x2="12" y2="3"></line>
    </svg>`;

    // --- STYLES ---
    const styles = `
        .ig-extractor-checkbox {
            position: absolute !important;
            top: 8px !important;
            left: 8px !important;
            z-index: 1000 !important;
            width: 20px !important;
            height: 20px !important;
            cursor: pointer !important;
            accent-color: #0095f6;
            outline: 2px solid white;
            box-shadow: 0 2px 4px rgba(0,0,0,0.5);
        }

        .ig-dl-popup {
            position: fixed !important;
            z-index: 2147483647 !important;
            background: rgba(38, 38, 38, 0.95);
            border-radius: 12px;
            padding: 8px;
            display: flex;
            flex-direction: column;
            gap: 6px;
            box-shadow: 0 4px 12px rgba(0,0,0,0.5);
            min-width: 180px;
            border: 1px solid rgba(255,255,255,0.1);
            backdrop-filter: blur(5px);
            font-family: -apple-system, system-ui, sans-serif;
        }
        .ig-dl-popup button {
            background: transparent;
            border: none;
            color: white;
            padding: 8px 12px;
            text-align: left;
            font-size: 13px;
            cursor: pointer;
            border-radius: 6px;
            transition: background 0.2s;
            white-space: nowrap;
        }
        .ig-dl-popup button:hover { background: rgba(255,255,255,0.1); }
        .ig-dl-popup .ig-divider { height: 1px; background: rgba(255,255,255,0.1); margin: 2px 0; }

        .ig-dl-warning {
            color: #ffcc00;
            font-size: 11px;
            padding: 4px 8px;
            background: rgba(255, 204, 0, 0.1);
            border-radius: 4px;
            margin-bottom: 4px;
            text-align: center;
        }

        .ig-top-left-container {
            z-index: 9999;
            display: none;
            gap: 4px;
            align-items: center;
            transition: top 0.2s ease;
        }

        .ig-sticky-mode {
            position: fixed !important;
            top: 0px !important;
            border-radius: 12px;
            backdrop-filter: blur(5px);
        }

        .ig-bottom-right-container {
            position: fixed;
            z-index: 2147483647;
            display: none;
            gap: 8px;
            align-items: center;
            bottom: 10px;
            right: 10px;
        }

        .ig-btn {
            display: flex;
            align-items: center;
            justify-content: center;
            padding: 6px 10px;
            border-radius: 14px;
            font-weight: 400;
            font-size: 12px;
            cursor: pointer;
            box-shadow: 0 4px 12px rgba(0,0,0,0.5);
            font-family: -apple-system, system-ui, sans-serif;
            transition: all 0.2s ease;
            border: none;
            color: #fff;
        }

        .ig-btn:hover { transform: scale(1.05); }

        .ig-btn-dl {
            background-color: #0095f6;
            border: 1px solid #0095f6;
        }
        .ig-btn-add { background-color: #26a269; }
        .ig-btn-clear { background-color: #e01b24; color: white; }

        .ig-single-dl-btn {
            background: transparent;
            border: none;
            cursor: pointer;
            padding: 4px 0px 4px 8px;
            display: inline-flex !important;
            align-items: center;
            justify-content: center;
            transition: transform 0.2s;
            color: inherit;
            z-index: 10001;
            position: relative;
        }
        .ig-single-dl-btn:hover { opacity: 0.7; transform: scale(1.1); }

        .ig-story-count-text {
            font-size: 13px;
            font-weight: 700;
            margin-left: 3px;
            opacity: 1;
        }
    `;

    const styleSheet = document.createElement("style");
    styleSheet.innerText = styles;
    document.head.appendChild(styleSheet);

    function getCleanUrl(anchorElement) {
        if (!anchorElement || !anchorElement.href) return null;
        return anchorElement.href.split('?')[0];
    }

    function log(msg, type = "info") {
        console.log(`[IG-DL] ${type === 'error' ? '❌' : 'ℹ️'} ${msg}`);
    }

    function init() {
        initBulkDownloader();
        initSingleButtonInjector();
        initStoryButtonInjector();
        updatePanelVisibility();
        alignButtonWithVolume();

        window.addEventListener('scroll', handleScroll);
        window.addEventListener('resize', () => {
            handleScroll();
            alignButtonWithVolume();
        });

        setInterval(alignButtonWithVolume, 1000);
    }

    function alignButtonWithVolume() {
        const container = document.getElementById('ig-bottom-right-panel');
        if (!container || container.style.display === 'none') return;

        let volBtn = document.getElementById('igDefaultVolume') ||
                     document.querySelector('.igDefaultVolume');

        if (!volBtn) {
             const allDivs = document.querySelectorAll('div, button, span');
             for (let el of allDivs) {
                 if (el.innerText === "100%" && (el.querySelector('svg') || el.closest('button'))) {
                     volBtn = el.closest('button') || el;
                     break;
                 }
             }
        }

        if (volBtn) {
            const rect = volBtn.getBoundingClientRect();
            if (rect.width > 0 && rect.height > 0) {
                const containerRect = container.getBoundingClientRect();
                container.style.left = (rect.left - containerRect.width - 8) + 'px';
                container.style.top = rect.top + 'px';
                container.style.bottom = 'auto';
                container.style.right = 'auto';
                return;
            }
        }

        container.style.position = 'fixed';
        container.style.right = '10px';
        container.style.bottom = '10px';
        container.style.top = 'auto';
        container.style.left = 'auto';
    }

    function handleScroll() {
        const topLeftPanel = document.getElementById('ig-top-left-panel');
        if (!topLeftPanel || topLeftPanel.style.display === 'none') return;

        const tabList = document.querySelector('div[role="tablist"]');
        if (tabList) {
            const rect = tabList.getBoundingClientRect();
            if (rect.top < 60) {
                if (!topLeftPanel.classList.contains('ig-sticky-mode')) {
                    topLeftPanel.classList.add('ig-sticky-mode');
                }
                topLeftPanel.style.left = rect.left + 'px';
                topLeftPanel.style.transform = 'none';
            } else {
                if (topLeftPanel.classList.contains('ig-sticky-mode')) {
                    topLeftPanel.classList.remove('ig-sticky-mode');
                    topLeftPanel.style.left = '0px';
                    topLeftPanel.style.top = '12px';
                }
            }
        }
        alignButtonWithVolume();
    }

    function filterUniqueUrls(urlArray) {
        const uniqueSet = new Set();
        return urlArray.filter(link => {
            if (uniqueSet.has(link)) return false;
            uniqueSet.add(link);
            return true;
        });
    }

    function getCurrentSlideIndex(article) {
        const ul = article.querySelector('ul');
        if (!ul) return 0;
        const lis = ul.querySelectorAll('li');
        if (lis.length === 0) return 0;
        const containerRect = ul.parentElement.getBoundingClientRect();
        const containerCenter = containerRect.left + (containerRect.width / 2);
        let closestIndex = 0;
        let minDistance = Infinity;
        lis.forEach((li, index) => {
            const liRect = li.getBoundingClientRect();
            const liCenter = liRect.left + (liRect.width / 2);
            const distance = Math.abs(containerCenter - liCenter);
            if (distance < minDistance) {
                minDistance = distance;
                closestIndex = index;
            }
        });
        return closestIndex;
    }

    // --- DOM STORY COUNTER ---
    function getStoryElements() {
        let bars = Array.from(document.querySelectorAll('div._ac3n > div'));
        if (bars.length === 0) {
            const specificBars = document.getElementsByClassName('x1lix1fw');
            if (specificBars.length > 1 && specificBars[0].parentElement) {
                bars = Array.from(specificBars[0].parentElement.children);
            }
        }
        if (bars.length === 0) {
            const headers = document.querySelectorAll('header');
            for (let header of headers) {
                const progressContainer = header.querySelector('div > div > div');
                if (progressContainer && progressContainer.childElementCount > 1) {
                    const firstChild = progressContainer.firstElementChild;
                    if (firstChild && firstChild.offsetHeight < 10) {
                        bars = Array.from(progressContainer.children);
                        break;
                    }
                }
            }
        }
        return bars;
    }

    function getStoryCountFromDOM() {
        return getStoryElements().length;
    }

    function markMissingStories(apiCount) {
        const bars = getStoryElements();
        const domCount = bars.length;
        if (domCount > 0 && apiCount < domCount) {
            bars.forEach((bar, index) => {
                if (index >= apiCount) {
                    bar.setAttribute('style', 'background-color: red !important;');
                } else {
                    bar.setAttribute('style', '');
                }
            });
        } else {
             bars.forEach(bar => bar.setAttribute('style', ''));
        }
    }

    function showChoicePopup(btnElement, videoUrls, onDownloadAll, onDownloadCurrent) {
        const existing = document.querySelector('.ig-dl-popup');
        if (existing) existing.remove();

        const popup = document.createElement('div');
        popup.className = 'ig-dl-popup';

        const apiCount = videoUrls.length;
        let isStory = window.location.href.includes('/stories/');
        let domCount = isStory ? getStoryCountFromDOM() : 0;

        if (isStory) markMissingStories(apiCount);

        if (isStory && domCount > 0 && apiCount < domCount) {
            const warning = document.createElement('div');
            warning.className = 'ig-dl-warning';
            warning.innerText = `⚠️ API Missing: ${domCount - apiCount} stories`;
            popup.appendChild(warning);
        }

        const rect = btnElement.getBoundingClientRect();
        popup.style.top = (rect.bottom + 8) + 'px';
        popup.style.left = rect.left + 'px';

        let currentIsAvailable = true;
        if (apiCount === 0) currentIsAvailable = false;

        const btnCurrent = document.createElement('button');
        if (currentIsAvailable) {
            btnCurrent.innerText = `Download visible`;
            btnCurrent.onclick = (e) => {
                e.stopPropagation(); popup.remove(); onDownloadCurrent();
            };
            popup.appendChild(btnCurrent);
        } else {
            btnCurrent.innerText = `Unavailable`;
            btnCurrent.disabled = true;
            btnCurrent.style.color = '#ff6b6b';
            popup.appendChild(btnCurrent);
        }

        const btnAll = document.createElement('button');
        btnAll.innerText = `Download All (${apiCount})`;
        btnAll.onclick = (e) => {
            e.stopPropagation(); popup.remove(); onDownloadAll();
        };

        const div = document.createElement('div'); div.className = 'ig-divider';
        popup.appendChild(div);
        popup.appendChild(btnAll);

        const closeListener = (e) => {
            if (!popup.contains(e.target) && e.target !== btnElement) {
                popup.remove();
                document.removeEventListener('click', closeListener);
            }
        };
        setTimeout(() => document.addEventListener('click', closeListener), 10);
        document.body.appendChild(popup);
    }

    function initStoryButtonInjector() {
        if (!window.location.href.includes('/stories/')) return;
        const controlIcons = document.querySelectorAll('svg[aria-label="Pause"], svg[aria-label="Play"], svg[aria-label="Duraklat"], svg[aria-label="Oynat"]');

        controlIcons.forEach(icon => {
            const parentButton = icon.closest('div[role="button"], button');
            if (parentButton) {
                const container = parentButton.parentElement;

                let storyCount = getStoryCountFromDOM();
                let dlBtn = container.querySelector('.ig-story-dl-btn');

                if (!dlBtn) {
                    if (window.getComputedStyle(container).display !== 'flex') {
                        container.style.display = 'flex';
                        container.style.alignItems = 'center';
                    }
                    dlBtn = document.createElement('div');
                    dlBtn.className = 'ig-single-dl-btn ig-story-dl-btn';
                    dlBtn.title = "Pause and Download";
                    dlBtn.style.color = "white";

                    dlBtn.onclick = async (e) => {
                        e.stopPropagation(); e.preventDefault();
                        const pauseBtn = document.querySelector('svg[aria-label="Pause"], svg[aria-label="Duraklat"]');
                        if (pauseBtn) {
                            const actualPauseClick = pauseBtn.closest('div[role="button"], button');
                            if (actualPauseClick) actualPauseClick.click();
                        }
                        setTimeout(() => handleDownloadClick(dlBtn, true), 300);
                    };
                    container.insertBefore(dlBtn, parentButton);
                }

                const currentCount = dlBtn.getAttribute('data-count');
                if (currentCount != storyCount) {
                    const countText = storyCount > 0 ? `(${storyCount})` : '';
                    dlBtn.innerHTML = DOWNLOAD_ICON + `<span class="ig-story-count-text">${countText}</span>`;
                    dlBtn.setAttribute('data-count', storyCount);
                }
            }
        });
    }

    function initSingleButtonInjector() {
        const saveIcons = document.querySelectorAll('svg[aria-label="Kaydet"], svg[aria-label="Save"], svg[aria-label="Remove"]');
        saveIcons.forEach(icon => {
            const parentButton = icon.closest('div[role="button"], button, a');
            if (parentButton) {
                const container = parentButton.parentElement;
                if (container.querySelector('.ig-single-dl-btn')) return;
                if (window.getComputedStyle(container).display !== 'flex') {
                    container.style.display = 'flex';
                    container.style.alignItems = 'center';
                }
                const dlBtn = document.createElement('div');
                dlBtn.className = 'ig-single-dl-btn';
                dlBtn.innerHTML = DOWNLOAD_ICON;
                dlBtn.title = "Download";
                dlBtn.style.marginLeft = '8px'
                dlBtn.style.color = getComputedStyle(icon).color;
                dlBtn.onclick = async (e) => {
                    e.stopPropagation(); e.preventDefault();
                    await handleDownloadClick(dlBtn, false);
                };
                container.insertBefore(dlBtn, parentButton.nextSibling);
            }
        });
    }

    async function handleDownloadClick(btn, isStory) {
        btn.style.color = "yellow";

        try {
            let url = window.location.href;
            let article = null;

            if (isStory) {
                // No strict check
            } else {
                article = btn.closest('article');
                if (!url.includes('/p/') && !url.includes('/stories/') && !url.includes('/reel/')) {
                    if (article) {
                        const linkElement = article.querySelector('a[href*="/p/"], a[href*="/reel/"]');
                        if (linkElement) url = linkElement.href;
                    }
                }
            }

            if (url.includes('/reel/')) url = url.replace('/reel/', '/p/');
            url = url.split('?')[0];

            let videoUrls = await fetchFromTiooApi(url);

            if (!videoUrls || videoUrls.length === 0) throw new Error("Link not found or the account you are trying to download is private!");

            videoUrls = filterUniqueUrls(videoUrls);

            // Mismatch logic
            const domCount = isStory ? getStoryCountFromDOM() : 0;
            const hasMismatch = isStory && domCount > 0 && videoUrls.length < domCount;

            if (videoUrls.length === 1 && !hasMismatch) {
                await downloadList(videoUrls, isStory ? "ig_story" : "ig_post");
                flashSuccess(btn);
            } else {
                btn.style.color = '';
                showChoicePopup(
                    btn,
                    videoUrls,
                    async () => {
                        await downloadList(videoUrls, isStory ? "ig_story" : "ig_post");
                        flashSuccess(btn);
                    },
                    async () => {
                        let index = 0;
                        if (!isStory && article) {
                            index = getCurrentSlideIndex(article);
                        }
                        if (index >= videoUrls.length) index = 0;
                        await downloadList([videoUrls[index]], (isStory ? "ig_story" : "ig_post") + "_selected");
                        flashSuccess(btn);
                    }
                );
            }
        } catch (err) {
            alert("Error: " + err);
            btn.style.color = "red";
        } finally {
            if (!document.querySelector('.ig-dl-popup')) flashSuccess(btn);
        }
    }

    function flashSuccess(btn) {
        const originalColor = btn.style.color;
        btn.style.color = "#26a269";
        setTimeout(() => btn.style.color = originalColor || "inherit", 2000);
        btn.style.opacity = "1";
    }

    async function downloadList(urls, prefix) {
        for (let i = 0; i < urls.length; i++) {
            const link = urls[i];
            let ext = "mp4";
            if (link.includes(".jpg") || link.includes(".jpeg")) ext = "jpg";
            const blob = await fetchFileBlob(link);
            saveAs(blob, `${prefix}_${Date.now()}_part${i+1}.${ext}`);
            if (urls.length > 1) await new Promise(r => setTimeout(r, 500));
        }
    }

    // --- CHECK FOR SELECTABLE PAGES ---
    function isSelectablePage() {
        const path = window.location.pathname;
        if (path === '/' || path.startsWith('/direct/') || path.startsWith('/settings/')) return false;
        if (path.startsWith('/explore/')) return true;
        if (path.startsWith('/stories/') || path.startsWith('/p/')) return false;
        if (path.startsWith('/reels/') && !path.includes('/reels/audio/')) return false;
        if (document.querySelector('div[role="tablist"]')) return true;
        return false;
    }

    function updatePanelVisibility() {
        const topLeftPanel = document.getElementById('ig-top-left-panel');
        const bottomRightPanel = document.getElementById('ig-bottom-right-panel');
        if (!topLeftPanel || !bottomRightPanel) return;

        // 1. Top Left Panel (Select Visible) - Only on Profile or Explore
        if (isSelectablePage()) {
            topLeftPanel.style.display = 'flex';

            const tabList = document.querySelector('div[role="tablist"]');
            if (tabList) {
                if (!topLeftPanel.classList.contains('ig-sticky-mode')) {
                    if (topLeftPanel.parentElement !== tabList.parentElement) {
                        tabList.parentElement.style.position = 'relative';
                        tabList.parentElement.appendChild(topLeftPanel);
                        topLeftPanel.style.position = 'absolute';
                        topLeftPanel.style.top = '12px';
                        topLeftPanel.style.left = '0px';
                        topLeftPanel.style.transform = 'none';
                    }
                }
            } else {
                if (!topLeftPanel.classList.contains('ig-sticky-mode')) {
                    if (topLeftPanel.parentElement !== document.body) {
                        document.body.appendChild(topLeftPanel);
                    }
                    topLeftPanel.classList.add('ig-sticky-mode');
                    topLeftPanel.style.position = 'fixed';
                    topLeftPanel.style.top = '70px';
                    topLeftPanel.style.left = '20px';
                }
            }
        } else {
            topLeftPanel.style.display = 'none';
        }

        // 2. Bottom Right Panel (Download Button) - ALWAYS if selected
        if (selectedUrls.size > 0) {
            bottomRightPanel.style.display = 'flex';
        } else {
            bottomRightPanel.style.display = 'none';
        }
    }

    function initBulkDownloader() {
        if (!document.getElementById('ig-top-left-panel')) {
            const topLeftContainer = document.createElement('div');
            topLeftContainer.id = 'ig-top-left-panel';
            topLeftContainer.className = 'ig-top-left-container';
            const addBtn = document.createElement('button');
            addBtn.innerText = 'Select Visible';
            addBtn.className = 'ig-btn ig-btn-add';
            addBtn.onclick = selectAllVisible;
            const clearBtn = document.createElement('button');
            clearBtn.innerText = 'X';
            clearBtn.className = 'ig-btn ig-btn-clear';
            clearBtn.onclick = clearAll;
            topLeftContainer.appendChild(addBtn);
            topLeftContainer.appendChild(clearBtn);
            document.body.appendChild(topLeftContainer);
        }
        if (!document.getElementById('ig-bottom-right-panel')) {
            const bottomRightContainer = document.createElement('div');
            bottomRightContainer.id = 'ig-bottom-right-panel';
            bottomRightContainer.className = 'ig-bottom-right-container';
            const dlBtn = document.createElement('button');
            dlBtn.id = 'ig-dl-btn';
            dlBtn.className = 'ig-btn ig-btn-dl';
            dlBtn.innerHTML = DOWNLOAD_ICON + '<span style="padding-left:8px">Download Selected</span>';
            dlBtn.onclick = startDirectDownload;
            bottomRightContainer.appendChild(dlBtn);
            document.body.appendChild(bottomRightContainer);
        }
        const targets = document.querySelectorAll('a[href*="/p/"], a[href*="/reel/"]');
        targets.forEach(el => {
            const hasMedia = el.querySelector('img') || el.querySelector('video') || el.querySelector('div[class*="_aagw"]') || el.querySelector('div[style*="background-image"]');

            if (!hasMedia) return;

            if (el.getAttribute('data-ig-checked') === 'true') return;
            el.setAttribute('data-ig-checked', 'true');
            if (window.getComputedStyle(el).position === 'static') el.style.position = 'relative';
            const cb = document.createElement('input');
            cb.type = 'checkbox';
            cb.className = 'ig-extractor-checkbox';
            const url = getCleanUrl(el);
            if (selectedUrls.has(url)) cb.checked = true;
            cb.addEventListener('change', (e) => {
                if (e.target.checked) selectedUrls.add(url);
                else selectedUrls.delete(url);
                updateCounter();
            });
            cb.addEventListener('click', (e) => { e.stopImmediatePropagation(); e.stopPropagation(); });
            el.prepend(cb);
        });
        updateCounter();
    }

    function updateCounter() {
        const btn = document.getElementById('ig-dl-btn');
        // FIX: Prevent overwrite if downloading
        if (btn && !isDownloading) {
            btn.innerHTML = DOWNLOAD_ICON + `<span>Download (${selectedUrls.size})</span>`;
        }
        updatePanelVisibility();
        alignButtonWithVolume();
    }

    async function startDirectDownload() {
        if (selectedUrls.size === 0) return alert("You must make a choice!");
        if (isDownloading) return;
        isDownloading = true;
        const btn = document.getElementById('ig-dl-btn');
        const originalHtml = btn.innerHTML;
        const urls = Array.from(selectedUrls);
        let success = 0, fail = 0;
        for (let i = 0; i < urls.length; i++) {
            // STATUS: FETCHING
            btn.innerHTML = DOWNLOAD_ICON + `<span style="margin-left: 4px;">Fetching ${i + 1}/${urls.length}...</span>`;

            let pageUrl = urls[i];
            try {
                if (pageUrl.includes('/reel/')) pageUrl = pageUrl.replace('/reel/', '/p/');
                let videoUrls = await fetchFromTiooApi(pageUrl);
                if (videoUrls && videoUrls.length > 0) {
                    // STATUS: DOWNLOADING
                    btn.innerHTML = DOWNLOAD_ICON + `<span style="margin-left: 4px;">Downloading ${i + 1}/${urls.length}...</span>`;

                    videoUrls = filterUniqueUrls(videoUrls);
                    await downloadList(videoUrls, `post_${i+1}`);
                    success++;
                } else fail++;
            } catch (e) { fail++; console.error(e); }
            await new Promise(r => setTimeout(r, 800));
        }

        isDownloading = false;
        // Restore original text immediately
        btn.innerHTML = DOWNLOAD_ICON + `<span>Download (${selectedUrls.size})</span>`;
        alert(`Done!\nSuccess: ${success}\nError: ${fail}`);
    }

    function fetchFromTiooApi(igUrl) {
        return new Promise((resolve, reject) => {
            const fullApiUrl = `${API_URL}?url=${encodeURIComponent(igUrl)}`;
            GM_xmlhttpRequest({
                method: "GET", url: fullApiUrl, headers: { "Accept": "application/json" },
                onload: function(response) {
                    if (response.status !== 200) return resolve(null);
                    try {
                        const json = JSON.parse(response.responseText);
                        let results = [];
                        if (json.result && Array.isArray(json.result)) results = json.result.map(item => item.url).filter(u => u);
                        else if (Array.isArray(json)) results = json.map(item => item.url).filter(u => u);
                        else if (json.url) results.push(json.url);
                        resolve(results.length > 0 ? results : null);
                    } catch (e) { resolve(null); }
                }, onerror: () => reject("Network"), ontimeout: () => reject("Time")
            });
        });
    }

    function fetchFileBlob(fileUrl) {
        return new Promise((resolve, reject) => {
            GM_xmlhttpRequest({
                method: "GET", url: fileUrl, responseType: "blob",
                onload: (res) => res.status === 200 ? resolve(res.response) : reject(res.status), onerror: reject
            });
        });
    }

    function selectAllVisible() {
        document.querySelectorAll('.ig-extractor-checkbox').forEach(cb => {
            if (cb.offsetParent !== null && !cb.checked) {
                cb.checked = true;
                const anchor = cb.closest('a');
                if (anchor) selectedUrls.add(getCleanUrl(anchor));
            }
        });
        updateCounter();
    }
    function clearAll() {
        document.querySelectorAll('.ig-extractor-checkbox').forEach(cb => cb.checked = false);
        selectedUrls.clear();
        updateCounter();
    }

    setInterval(init, CHECK_INTERVAL);

})();