Instagram Link Collector

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

Vous devrez installer une extension telle que Tampermonkey, Greasemonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Userscripts pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension de gestionnaire de script utilisateur pour installer ce script.

(J'ai déjà un gestionnaire de scripts utilisateur, laissez-moi l'installer !)

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

(J'ai déjà un gestionnaire de style utilisateur, laissez-moi l'installer!)

// ==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();
})();