Instagram Link Collector

Collect Instagram post URLs during auto-scroll. Edge hover UI, idle-stop, clipboard copy, .txt export.

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Instagram Link Collector
// @namespace    https://www.instagram.com/
// @version      1.1.5
// @description  Collect Instagram post URLs during auto-scroll. Edge hover UI, idle-stop, clipboard copy, .txt export.
// @author       Gemini 3 Pro (previously ChatGPT 5.2 Thinking)
// @icon         https://web.archive.org/web/20171228000551im_/https://www.instagram.com/static/images/ico/favicon.ico/dfa85bb1fd63.ico
// @match        https://www.instagram.com/*
// @grant        GM_addStyle
// @grant        GM_setClipboard
// @license      MIT
// ==/UserScript==

(function () {
    "use strict";

    var collected = new Set();
    var intervalId = null;
    var countdownInterval = null;
    var lastHeight = 0;
    var idleCounter = 0;

    var DEFAULT_IDLE_SECONDS = 33;

    var TAB_W = 18;
    var TAB_H = 44;
    var TAB_TEXT_PAD = TAB_W + 9;
    var PANEL_W = 340;

    var hideTimer = null;
    var isShown = false;
    var pinnedUntil = 0;

    function nowMs() { return Date.now(); }

    function pinBriefly(ms) {
        pinnedUntil = nowMs() + (ms || 2000);
        showPanel(true);
    }

    function shouldBlockHide() {
        return nowMs() <= pinnedUntil || intervalId !== null || countdownInterval !== null;
    }

    function normalizeUrl(u) {
        if (!u) return "";
        return u.split("?")[0].split("#")[0].replace(/\/+$/, "");
    }

    function updateOutput() {
        var list = Array.from(collected).sort();
        output.value = list.join("\n");
        counter.textContent = list.length + " links";
        if (!intervalId && status.textContent === "Scrolling...") status.textContent = "Idle";
        if (intervalId && status.textContent !== "Scrolling...") status.textContent = "Scrolling...";
    }

    function isLoaderVisible() {
        return !!document.querySelector('svg[aria-label="Loading..."]');
    }

    function collectLinks() {
        var links = document.querySelectorAll('a[href*="/p/"]');
        var initial = collected.size;

        for (var i = 0; i < links.length; i++) {
            var href = links[i].href;
            if (!href) continue;
            collected.add(normalizeUrl(href));
        }

        if (collected.size !== initial) {
            updateOutput();
            return true;
        }
        return false;
    }

    function stopScroll(reason) {
        if (intervalId) {
            clearInterval(intervalId);
            intervalId = null;
        }
        if (countdownInterval) {
            clearInterval(countdownInterval);
            countdownInterval = null;
        }
        status.textContent = reason || "Stopped";
        updateOutput();
    }

    function autoSave() {
        var urls = Array.from(collected);
        if (!urls.length) return;

        var username = "instagram";
        var pathMatch = window.location.pathname.match(/^\/([a-zA-Z0-9_.]+)/);
        if (pathMatch && pathMatch[1]) {
            var bad = ["p", "explore", "direct", "reels", "stories", "accounts"];
            if (bad.indexOf(pathMatch[1]) === -1) username = pathMatch[1];
        }

        var d = new Date();
        var yyyy = d.getFullYear();
        var mm = String(d.getMonth() + 1).padStart(2, "0");
        var dd = String(d.getDate()).padStart(2, "0");
        var dateStr = yyyy + "-" + mm + "-" + dd;

        var filename = username + "_instagram-urls_" + dateStr + ".txt";

        var blob = new Blob([urls.join("\n")], { type: "text/plain" });
        var url = URL.createObjectURL(blob);
        var a = document.createElement("a");
        a.href = url;
        a.download = filename;
        document.body.appendChild(a);
        a.click();
        document.body.removeChild(a);
        URL.revokeObjectURL(url);
    }

    function autoScrollTick() {
        var currentHeight = document.body.scrollHeight;
        window.scrollBy(0, window.innerHeight);

        var newLinksFound = collectLinks();
        var idleThresholdTicks = Math.max(1, Math.floor(parseInt(idleInput.value, 10) / 2));

        if (newLinksFound || currentHeight > lastHeight || isLoaderVisible()) {
            idleCounter = 0;
            lastHeight = currentHeight;
        } else {
            idleCounter++;
        }

        if (idleCounter >= idleThresholdTicks) {
            stopScroll("Auto-stopped (idle)");
            autoSave();
        }
    }

    function startScroll() {
        if (intervalId || countdownInterval) return;

        var countdown = 3;
        status.textContent = "Starting in " + countdown + "...";
        pinBriefly(3000);

        countdownInterval = setInterval(function () {
            countdown--;
            if (countdown > 0) {
                status.textContent = "Starting in " + countdown + "...";
            } else {
                clearInterval(countdownInterval);
                countdownInterval = null;

                lastHeight = document.body.scrollHeight;
                idleCounter = 0;
                intervalId = setInterval(autoScrollTick, 2000);
                status.textContent = "Scrolling...";
                collectLinks();
                updateOutput();
            }
        }, 1000);
    }

    function copyAll() {
        GM_setClipboard(output.value || "");
        status.textContent = "Copied";
        setTimeout(function () { status.textContent = intervalId ? "Scrolling..." : "Idle"; }, 900);
    }

    function downloadTxt() {
        autoSave();
        status.textContent = "Downloaded";
        setTimeout(function () { status.textContent = intervalId ? "Scrolling..." : "Idle"; }, 900);
    }

    function clearAll() {
        collected.clear();
        updateOutput();
        status.textContent = "Cleared";
        setTimeout(function () { status.textContent = intervalId ? "Scrolling..." : "Idle"; }, 900);
    }

    GM_addStyle(
        '#ig_hover_tab{' +
        'position:fixed;left:0;top:50%;width:' + TAB_W + 'px;height:' + TAB_H + 'px;margin-top:-' + Math.floor(TAB_H / 2) + 'px;' +
        'z-index:100000;cursor:pointer;opacity:0.7;background-color:#bdc5c8;' +
        'border:1px solid #abb0b3;border-left:none;border-radius:0 5px 5px 0;' +
        'display:flex;align-items:center;justify-content:center;' +
        '}' +
        '#ig_hover_tab:hover{opacity:1;}' +
        '#ig_hover_tab svg{display:block;}' +
        '#ig_panel{' +
        'position:fixed;left:0;top:50%;width:' + PANEL_W + 'px;z-index:99999;' +
        'transform:translateX(-100%);transition:transform 140ms linear,opacity 140ms linear;opacity:0.92;' +
        'background:rgba(0,0,0,0.75);color:#fff;padding:10px 10px 10px ' + TAB_TEXT_PAD + 'px;' +
        'border-radius:0 8px 8px 0;font-size:12px;box-shadow:0 2px 10px rgba(0,0,0,0.35);' +
        '}' +
        '#ig_panel.ig_show{transform:translateX(0);opacity:1;}' +
        '#ig_title{font-weight:700;margin:0 0 4px 0;}' +
        '#ig_row{display:flex;align-items:center;justify-content:space-between;gap:8px;margin:0;}' +
        '#ig_status{opacity:0.9;font-size:11px;}' +
        '#ig_actions_row1{display:flex;flex-wrap:wrap;gap:6px;margin-top:8px;margin-bottom:6px;}' +
        '#ig_actions_row2{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:8px;align-items:center;}' +
        '#ig_panel button{' +
        'padding:4px 10px;font-size:12px;cursor:pointer;background:rgba(255,255,255,0.08);color:#fff;' +
        'border:1px solid rgba(255,255,255,0.25);border-radius:999px;line-height:1.2;user-select:none;' +
        '}' +
        '#ig_panel button:hover{background:rgba(255,255,255,0.18);}' +
        '#ig_panel button:active{background:rgba(255,255,255,0.28);}' +
        // CHANGE: Added !important and solid colors to force visibility
        '#ig_start{border:1px solid #4CAF50 !important; box-shadow: 0 0 3px #4CAF50;}' +
        '#ig_stop{border:1px solid #F44336 !important; box-shadow: 0 0 3px #F44336;}' +
        '#ig_clear{border:1px solid #FFEB3B !important; box-shadow: 0 0 3px #FFEB3B; color: #fff;}' +
        '#ig_idle_wrap{display:flex;align-items:center;gap:6px;}' +
        '#ig_idle_wrap span{font-size:11px;opacity:0.9;}' +
        '#ig_idle{width:56px;font-size:12px;padding:2px 6px;border-radius:8px;border:1px solid rgba(255,255,255,0.25);background:rgba(0,0,0,0.35);color:#fff;}' +
        '#ig_output{width:100%;height:180px;margin-top:6px;font-size:11px;resize:vertical;}'
    );

    var tab = document.createElement("div");
    tab.id = "ig_hover_tab";
    tab.innerHTML =
        '<svg width="12" height="12" viewBox="0 0 12 12" aria-hidden="true">' +
        '<path id="ig_arrow_path" d="M4 2 L8 6 L4 10 Z" fill="#2b2f33"></path>' +
        '</svg>';
    document.body.appendChild(tab);

    var arrowPath = document.getElementById("ig_arrow_path");

    var panel = document.createElement("div");
    panel.id = "ig_panel";
    panel.innerHTML =
        '<div id="ig_header">' +
            '<div id="ig_title">Instagram Link Collector</div>' +
            '<div id="ig_row">' +
                '<div id="ig_counter">0 links</div>' +
                '<div id="ig_status">Idle</div>' +
            '</div>' +
        '</div>' +
        '<div id="ig_actions_row1">' +
            '<button id="ig_start" type="button">Start scroll</button>' +
            '<button id="ig_stop" type="button">Stop</button>' +
            '<button id="ig_copy" type="button">Copy</button>' +
            '<button id="ig_clear" type="button">Clear</button>' +
        '</div>' +
        '<div id="ig_actions_row2">' +
            '<button id="ig_txt" type="button">Download .txt</button>' +
            '<div id="ig_idle_wrap"><span>Idle (s)</span><input id="ig_idle" type="number" min="2" value="' + DEFAULT_IDLE_SECONDS + '"></div>' +
        '</div>' +
        '<textarea id="ig_output" readonly></textarea>';
    document.body.appendChild(panel);

    var headerWrap = document.getElementById("ig_header");
    var counter = document.getElementById("ig_counter");
    var status = document.getElementById("ig_status");
    var output = document.getElementById("ig_output");
    var idleInput = document.getElementById("ig_idle");

    function setArrow(open) {
        arrowPath.setAttribute("d", open ? "M8 2 L4 6 L8 10 Z" : "M4 2 L8 6 L4 10 Z");
    }

    function setPanelTopToTab() {
        var tabRect = tab.getBoundingClientRect();
        var headerH = headerWrap.getBoundingClientRect().height;
        var top = Math.round(tabRect.top - headerH - 6);
        panel.style.top = top + "px";
    }

    function showPanel(force) {
        if (hideTimer) {
            clearTimeout(hideTimer);
            hideTimer = null;
        }
        setPanelTopToTab();
        if (isShown && !force) return;
        isShown = true;
        panel.classList.add("ig_show");
        setArrow(true);
    }

    function scheduleHide() {
        if (shouldBlockHide()) return;

        if (hideTimer) clearTimeout(hideTimer);
        hideTimer = setTimeout(function () {
            if (shouldBlockHide()) return;
            isShown = false;
            panel.classList.remove("ig_show");
            setArrow(false);
        }, 140);
    }

    tab.addEventListener("mouseenter", function () { showPanel(false); });
    tab.addEventListener("mouseleave", scheduleHide);
    panel.addEventListener("mouseenter", function () { showPanel(true); });
    panel.addEventListener("mouseleave", scheduleHide);

    window.addEventListener("resize", function () {
        if (isShown) setPanelTopToTab();
    }, { passive: true });

    document.getElementById("ig_start").addEventListener("click", function (e) {
        e.preventDefault();
        pinBriefly(3000);
        startScroll();
    });

    document.getElementById("ig_stop").addEventListener("click", function (e) {
        e.preventDefault();
        pinBriefly(3000);
        stopScroll("Stopped");
    });

    document.getElementById("ig_copy").addEventListener("click", function (e) {
        e.preventDefault();
        pinBriefly(3000);
        copyAll();
    });

    document.getElementById("ig_txt").addEventListener("click", function (e) {
        e.preventDefault();
        pinBriefly(3000);
        downloadTxt();
    });

    document.getElementById("ig_clear").addEventListener("click", function (e) {
        e.preventDefault();
        pinBriefly(3000);
        clearAll();
    });

    setArrow(false);
    collectLinks();
    updateOutput();
})();