批量打開 reddit 的 r/udemyfreeebies 連結(自訂 UI)

自訂每批數量、每批提示,穩定不因切頁被中斷

// ==UserScript==
// @name         批量打開 reddit 的 r/udemyfreeebies 連結(自訂 UI)
// @namespace    http://tampermonkey.net/
// @version      0.12
// @description  自訂每批數量、每批提示,穩定不因切頁被中斷
// @license      GPL-3.0
// @match        https://*/*
// @grant        GM_registerMenuCommand
// @author       twozwu
// ==/UserScript==

let links = [];
let currentIndex = 0;
let perClick = parseInt(localStorage.getItem('defaultPerClick')) || 15;
let resumeLast = localStorage.getItem('resumeCheck') === 'true';

function createUI() {
    const panel = document.createElement('div');
    panel.id = "bulkOpenerPanel";
    panel.style = `
        position: fixed;
        top: 80px;
        right: 20px;
        background: #f0f0f0;
        border: 2px solid #ccc;
        border-radius: 12px;
        padding: 15px;
        z-index: 9999;
        box-shadow: 0 0 10px rgba(0,0,0,0.2);
        font-family: sans-serif;
        width: 240px;
    `;
    panel.innerHTML = `
        <h3 style="margin: 0 0 10px 0;">🔗 批量開啟設定</h3>
        <label>每批數量:
            <input id="perClick" type="number" value="${perClick}" min="1" style="width: 4rem;">
        </label><br><br>
        <label><input id="resumeCheck" type="checkbox" ${resumeLast ? 'checked' : ''}> 從上次進度</label><br>
        <label><input id="isStart" type="checkbox"> 從第
            <input id="indexKey" type="number" value="${localStorage.getItem('indexKey') || 0}" style="width: 4rem;">
        個開始</label><br><br>

        <button id="runOpener" style="padding: 5px 10px;">🚀 開始</button>
        <button id="closePanel" style="float:right;">❌</button>

        <div id="controlArea" style="margin-top:10px; display:none;">
            <p id="statusText" style="margin: 8px 0;"></p>
            <button id="continueBtn">▶️ 繼續</button>
            <button id="stopBtn">🛑 停止</button>
        </div>
    `;
    document.body.appendChild(panel);

    document.getElementById("closePanel").onclick = () => panel.remove();
    document.getElementById("resumeCheck").onchange = e => {
        localStorage.setItem('resumeCheck', e.target.checked);
    };

    document.getElementById("runOpener").onclick = () => {
        perClick = parseInt(document.getElementById("perClick").value);
        localStorage.setItem('defaultPerClick', perClick);

        const resume = document.getElementById("resumeCheck").checked;
        const isStart = document.getElementById("isStart").checked;
        currentIndex = 0;

        if (isStart) {
            currentIndex = parseInt(document.getElementById("indexKey").value) || 0;
        } else if (resume) {
            currentIndex = parseInt(localStorage.getItem('indexKey') || '0');
        }

        gatherLinks();
        if (currentIndex >= links.length) {
            updateProgress();
            return;
        }
        document.getElementById("controlArea").style.display = 'block';
        openBatch();
    };

    document.getElementById("continueBtn").onclick = () => openBatch();
    document.getElementById("stopBtn").onclick = () => {
        alert("⏹️ 已停止,進度已保存");
    };
}

function gatherLinks() {
    links = [];
    document.querySelectorAll('.text-neutral-content ul li a').forEach(anchor => {
        if (anchor.href) links.push(anchor.href);
    });

    if (links.length === 0) {
        alert("⚠️ 找不到連結!");
    }
}
function updateProgress() {
    if (currentIndex >= links.length) {
        document.getElementById("controlArea").style.display = 'none';
        //document.getElementById("statusText").textContent = "✅ 所有連結已打開完畢!(自動完成)";
        alert("✅ 所有連結已打開完畢!");
        localStorage.removeItem('indexKey');
    } else {
        document.getElementById("statusText").textContent = `已打開 ${currentIndex} / ${links.length},繼續下一批?`;
    }
}

function openBatch() {
    if (currentIndex >= links.length) {
        updateProgress();
        return;
    }

    const end = Math.min(currentIndex + perClick, links.length);
    for (let i = currentIndex; i < end; i++) {
        const win = window.open(links[i], "_blank", "noopener,noreferrer");
        if (win) {
            win.blur();
            window.focus();
        }
    }

    currentIndex = end;
    localStorage.setItem('indexKey', currentIndex);

    updateProgress(); // 自動監聽並更新
}

GM_registerMenuCommand("📂 開啟連結設定面板", createUI);