// ==UserScript== // @name Moledao Bulk Helper // @namespace https://intern.moledao.io/ // @version 0.5.3 // @description Batch claim XP rewards and assist pending CUBER mint on intern.moledao.io // @match https://intern.moledao.io/* // @run-at document-idle // @grant none // ==/UserScript== (function () { "use strict"; const PANEL_ID = "moledao-bulk-helper"; const POS_KEY = "moledao-helper-position"; const COLLAPSED_KEY = "moledao-helper-collapsed"; const MINT_QUEUE_KEY = "moledao-helper-mint-queue"; const VERSION = "0.5.3"; const API_BASE = `${location.origin}/api`; const STATE = { running: false, stopRequested: false, logs: [], mintWatcherStarted: false, }; const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); function beep() { try { const AudioContextClass = window.AudioContext || window.webkitAudioContext; if (!AudioContextClass) return; const ctx = new AudioContextClass(); const osc = ctx.createOscillator(); const gain = ctx.createGain(); osc.type = "sine"; osc.frequency.value = 880; gain.gain.value = 0.03; osc.connect(gain); gain.connect(ctx.destination); osc.start(); osc.stop(ctx.currentTime + 0.12); setTimeout(() => ctx.close().catch(() => {}), 200); } catch (_) { // ignore } } function normalize(text) { return (text || "").replace(/\s+/g, " ").trim(); } function flashPanel() { const panel = document.getElementById(PANEL_ID); if (!panel) return; panel.classList.remove("mh-flash"); void panel.offsetWidth; panel.classList.add("mh-flash"); } function clearMintHighlight() { document.querySelectorAll("[data-moledao-mint-highlight='1']").forEach((el) => { el.style.outline = ""; el.style.outlineOffset = ""; el.style.boxShadow = ""; el.removeAttribute("data-moledao-mint-highlight"); }); } function highlightMintButton(el) { if (!el) return; clearMintHighlight(); el.setAttribute("data-moledao-mint-highlight", "1"); el.style.outline = "3px solid #ffb300"; el.style.outlineOffset = "4px"; el.style.boxShadow = "0 0 0 8px rgba(255, 179, 0, 0.18)"; } function renderLogs() { const logBox = document.querySelector(`#${PANEL_ID} .mh-log`); if (!logBox) return; logBox.innerHTML = STATE.logs .slice(0, 8) .map((item) => `
${item}
`) .join(""); } function log(message) { console.log(`[MoledaoHelper] ${message}`); STATE.logs.unshift(`${new Date().toLocaleTimeString()} ${message}`); renderLogs(); const status = document.querySelector(`#${PANEL_ID} .mh-status`); if (status) status.textContent = message; } function getToken() { return localStorage.getItem("accessToken") || localStorage.getItem("moledao_token") || ""; } async function apiFetch(path, options = {}) { const token = getToken(); if (!token) { throw new Error("未找到 accessToken,请先确保你已登录平台"); } const response = await fetch(`${API_BASE}${path}`, { ...options, headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}`, ...(options.headers || {}), }, }); const data = await response.json().catch(() => ({})); if (!response.ok) { throw new Error(data.error || `请求失败 ${response.status}`); } return data; } function isObject(value) { return value && typeof value === "object" && !Array.isArray(value); } function findArrays(root) { const arrays = []; const visited = new Set(); function walk(value) { if (!value || typeof value !== "object") return; if (visited.has(value)) return; visited.add(value); if (Array.isArray(value)) { arrays.push(value); for (const item of value) walk(item); return; } for (const next of Object.values(value)) walk(next); } walk(root); return arrays; } function getNested(obj, keys) { for (const key of keys) { if (obj && obj[key] !== undefined && obj[key] !== null) return obj[key]; } return undefined; } function maybeClaimableTask(item) { if (!isObject(item)) return null; const id = getNested(item, [ "user_task_id", "userTaskId", "task_id", "taskId", "id", ]); const questId = getNested(item, ["quest_id", "questId"]); const title = normalize( getNested(item, ["title", "quest_title", "questTitle", "name"]) || "未命名任务" ); const statusBlob = normalize( [ getNested(item, ["status", "task_status", "taskStatus"]), getNested(item, ["review_status", "reviewStatus"]), getNested(item, ["reward_status", "rewardStatus"]), getNested(item, ["display_status", "displayStatus"]), getNested(item, ["claim_status", "claimStatus"]), getNested(item, ["label", "badge"]), ] .filter(Boolean) .join(" ") ).toLowerCase(); const approved = /已通过|approved/.test(statusBlob) || getNested(item, ["approved", "is_approved", "isApproved"]) === true; const claimable = /奖励待领|claimable|unclaimed|pending_claim/.test(statusBlob) || getNested(item, ["claimable", "can_claim", "canClaim"]) === true; const claimed = /已领取|claimed|rewarded/.test(statusBlob) || getNested(item, ["claimed", "is_claimed", "isClaimed"]) === true; if (!id || !title) return null; if ((approved || claimable) && !claimed) { return { id, questId, title, raw: item, }; } return null; } function extractClaimableTasks(payload) { const results = new Map(); const arrays = findArrays(payload); for (const arr of arrays) { for (const item of arr) { const task = maybeClaimableTask(item); if (task) results.set(String(task.id), task); } } return [...results.values()]; } function maybeMintableTask(item) { if (!isObject(item)) return null; const id = getNested(item, [ "user_task_id", "userTaskId", "task_id", "taskId", "id", ]); const questId = getNested(item, ["quest_id", "questId"]); const title = normalize( getNested(item, ["title", "quest_title", "questTitle", "name"]) || "未命名任务" ); const statusBlob = normalize( [ getNested(item, ["status", "task_status", "taskStatus"]), getNested(item, ["review_status", "reviewStatus"]), getNested(item, ["reward_status", "rewardStatus"]), getNested(item, ["display_status", "displayStatus"]), getNested(item, ["mint_status", "mintStatus"]), getNested(item, ["claim_status", "claimStatus"]), getNested(item, ["label", "badge"]), ] .filter(Boolean) .join(" ") ).toLowerCase(); const mintable = /cuber 待铸造|pending mint|pending_mint|mintable/.test(statusBlob) || getNested(item, ["can_mint", "canMint", "mintable"]) === true; const minted = /已铸造|minted|confirmed/.test(statusBlob) || getNested(item, ["minted", "is_minted", "isMinted"]) === true; if (!id || !title) return null; if (mintable && !minted) { return { id, questId, title, raw: item, }; } return null; } function extractMintableTasks(payload) { const results = new Map(); const arrays = findArrays(payload); for (const arr of arrays) { for (const item of arr) { const task = maybeMintableTask(item); if (task) results.set(String(task.id), task); } } return [...results.values()]; } async function loadClaimableTasks() { const payload = await apiFetch("/my-tasks"); const tasks = extractClaimableTasks(payload); return { payload, tasks }; } async function loadMintableTasks() { const payload = await apiFetch("/my-tasks"); const tasks = extractMintableTasks(payload); return { payload, tasks }; } function isVisible(el) { if (!el) return false; const rect = el.getBoundingClientRect(); const style = window.getComputedStyle(el); return rect.width > 0 && rect.height > 0 && style.display !== "none" && style.visibility !== "hidden"; } function loadMintableTasksFromDom() { const tasks = []; const seenTitles = new Set(); const candidates = [...document.querySelectorAll("div, article, section, a, button")] .filter((el) => isVisible(el)) .filter((el) => { const text = normalize(el.innerText || el.textContent || ""); const rect = el.getBoundingClientRect(); return ( text.includes("CUBER 待铸造") && rect.width > 180 && rect.height > 120 && rect.width < 500 && rect.height < 500 ); }); for (const el of candidates) { const lines = (el.innerText || "") .split("\n") .map((line) => normalize(line)) .filter(Boolean) .filter((line) => !/^(CUBER 待铸造|任务已截止|永久|通用|赏金|XP|\d+)$/.test(line)) .filter((line) => !/^\d+\s*\/\s*\d+$/.test(line)) .filter((line) => !/^已通过|未开始|审批中|已领取|奖励待领|已驳回$/.test(line)); const title = lines[0]; if (!title || seenTitles.has(title)) continue; seenTitles.add(title); tasks.push({ id: `dom-${tasks.length + 1}`, title, raw: { source: "dom-card", element: el }, }); } if (tasks.length === 0) { const bodyText = normalize(document.body.innerText || ""); const badgeCount = (bodyText.match(/CUBER 待铸造/g) || []).length; if (badgeCount > 0) { tasks.push({ id: "dom-body-count", title: `页面上检测到 ${badgeCount} 个待铸造标记`, raw: { source: "dom-body-count", badgeCount }, }); } } return tasks; } async function claimViaApi() { if (STATE.running) return; STATE.running = true; STATE.stopRequested = false; try { log("开始拉取可领取 XP 列表"); const { payload, tasks } = await loadClaimableTasks(); if (tasks.length === 0) { log("接口里没找到可领 XP 任务"); console.debug("[MoledaoHelper] /my-tasks payload", payload); return; } log(`找到 ${tasks.length} 个可领 XP 任务`); let successCount = 0; let failCount = 0; for (const task of tasks) { if (STATE.stopRequested) { log("停止请求已生效"); break; } try { log(`领取中: ${task.title} (#${task.id})`); await apiFetch(`/user-tasks/${task.id}/claim`, { method: "POST" }); successCount += 1; log(`领取成功: ${task.title}`); await sleep(300); } catch (error) { failCount += 1; log(`领取失败: ${task.title} - ${error.message || error}`); } } log(`XP 流程结束,成功 ${successCount},失败 ${failCount}`); } catch (error) { console.error(error); log(`XP 流程异常: ${error.message || error}`); } finally { STATE.running = false; } } function byText(root, selector, pattern) { return [...root.querySelectorAll(selector)].find((el) => pattern.test(normalize(el.textContent))); } function allByText(root, selector, pattern) { return [...root.querySelectorAll(selector)].filter((el) => pattern.test(normalize(el.textContent))); } function getMintCards() { const results = []; const seenTitles = new Set(); for (const task of loadMintableTasksFromDom()) { if (task.raw?.source !== "dom-card") continue; if (seenTitles.has(task.title)) continue; seenTitles.add(task.title); results.push({ title: task.title, card: task.raw.element, heading: null, }); } return results; } async function ensureMyTasksPage() { const myTasksButton = byText(document, "button", /^我的任务$/); if (myTasksButton) myTasksButton.click(); await sleep(1200); } async function waitFor(predicate, timeoutMs = 12000, intervalMs = 300) { const start = Date.now(); while (Date.now() - start < timeoutMs) { const value = predicate(); if (value) return value; await sleep(intervalMs); } return null; } function clickElement(el) { if (!el) return false; const rect = el.getBoundingClientRect(); const clickTarget = document.elementFromPoint( rect.left + rect.width / 2, rect.top + rect.height / 2 ) || el; const options = { bubbles: true, cancelable: true, view: window, clientX: rect.left + rect.width / 2, clientY: rect.top + rect.height / 2, }; clickTarget.dispatchEvent(new PointerEvent("pointerdown", options)); clickTarget.dispatchEvent(new MouseEvent("mousedown", options)); clickTarget.dispatchEvent(new PointerEvent("pointerup", options)); clickTarget.dispatchEvent(new MouseEvent("mouseup", options)); clickTarget.dispatchEvent(new MouseEvent("click", options)); if (typeof clickTarget.click === "function") clickTarget.click(); triggerReactClick(clickTarget, options); return true; } function triggerReactClick(el, options = {}) { let node = el; while (node && node !== document.body) { for (const key of Object.keys(node)) { if (!key.startsWith("__reactProps") && !key.startsWith("__reactEventHandlers")) continue; const props = node[key]; if (props && typeof props.onClick === "function") { props.onClick({ type: "click", target: el, currentTarget: node, nativeEvent: { isTrusted: true, ...options }, isTrusted: true, bubbles: true, cancelable: true, preventDefault() {}, stopPropagation() {}, }); return true; } } node = node.parentElement; } return false; } function closestClickable(el) { let node = el; while (node && node !== document.body) { if ( node.matches?.("button, a, [role='button']") || typeof node.onclick === "function" || node.getAttribute?.("tabindex") === "0" ) { return node; } node = node.parentElement; } return el; } function elementArea(el) { const rect = el.getBoundingClientRect(); return Math.max(1, rect.width * rect.height); } function saveMintQueue(queue) { localStorage.setItem(MINT_QUEUE_KEY, JSON.stringify(queue)); } function loadMintQueue() { try { const raw = localStorage.getItem(MINT_QUEUE_KEY); return raw ? JSON.parse(raw) : null; } catch (_) { return null; } } function clearMintQueue() { localStorage.removeItem(MINT_QUEUE_KEY); } function isMintSuccessScreen() { const text = normalize(document.body.innerText || ""); return /Cuber 铸造成功|成功添加到你的钱包|已成功添加到你的钱包/.test(text); } async function handleMintSuccessIfNeeded() { const queue = loadMintQueue(); if (!queue || !queue.active || !queue.currentTitle) return false; if (!isMintSuccessScreen()) return false; log(`检测到已完成: ${queue.currentTitle}`); clearMintHighlight(); const backButton = byText(document, "button", /^返回任务列表$/) || byText(document, "button", /^返回步骤$/) || byText(document, "button", /^Back to Discover$/i); if (backButton) { clickElement(backButton); log("已点击返回任务列表"); await sleep(1500); } queue.index += 1; queue.currentTitle = null; saveMintQueue(queue); if (queue.index >= queue.tasks.length) { log(`待铸造流程结束,已完成 ${queue.tasks.length}/${queue.tasks.length} 个任务`); clearMintQueue(); return true; } await sleep(1000); await resumeMintQueueIfNeeded(true); return true; } function findCardEntryByTitle(title) { return getMintCards().find((entry) => entry.title === title) || null; } async function closeTaskDetail() { const button = byText(document, "button", /^Back to Discover$/i) || byText(document, "button", /^返回$/) || byText(document, "button", /^关闭$/); if (button) { clickElement(button); await sleep(1200); return; } window.history.back(); await sleep(1200); } function findMintAction() { const exactButton = byText(document, "button", /^铸造\s*Cuber$/i) || byText(document, "button", /^Mint Cuber$/i) || byText(document, "button", /^Mint$/i) || byText(document, "button", /^铸造$/); if (exactButton) return exactButton; const candidates = [ ...allByText(document, "button", /Mint Cuber|Mint|铸造/i), ...allByText(document, "a", /Mint Cuber|Mint|铸造\s*Cuber|铸造/i), ...allByText(document, "[role='button']", /Mint Cuber|Mint|铸造/i), ...allByText(document, "div", /Mint Cuber|Mint|铸造/i), ...allByText(document, "span", /Mint Cuber|Mint|铸造/i), ].filter(isVisible); return candidates.find((el) => { const text = normalize(el.textContent || ""); return text.length <= 30 && !/批量|开始|检查/.test(text); }) || null; } function findMintActionContainer() { const exactTextCandidates = [...document.querySelectorAll("button, a, div, span")] .filter(isVisible) .filter((el) => /^(铸造\s*Cuber|Mint Cuber|铸造|Mint)$/i.test(normalize(el.textContent || ""))) .sort((a, b) => elementArea(a) - elementArea(b)); if (exactTextCandidates.length > 0) { return closestClickable(exactTextCandidates[0]); } const fallback = findMintAction(); return fallback ? closestClickable(fallback) : null; } async function triggerMintForTask(task, index, total) { const entry = findCardEntryByTitle(task.title); if (!entry) { log(`页面上找不到任务卡片: ${task.title}`); return false; } log(`处理 ${index}/${total}: ${task.title}`); clickElement(entry.heading || entry.card); await sleep(1500); const switchWalletButton = byText(document, "button", /切换到已绑定钱包|切换钱包/i); if (switchWalletButton) { clickElement(switchWalletButton); log(`已点击切换钱包: ${task.title}`); await sleep(1500); } const mintButton = await waitFor(findMintActionContainer, 12000, 400); if (!mintButton) { log(`未找到铸造按钮: ${task.title}`); return false; } const rect = mintButton.getBoundingClientRect(); log( `找到铸造按钮: ${normalize(mintButton.textContent || mintButton.innerText || "")} ` + `[${Math.round(rect.width)}x${Math.round(rect.height)}]` ); highlightMintButton(mintButton); beep(); log(`请手动点击“铸造 Cuber”并在 MetaMask 确认: ${task.title}`); return true; } async function resumeMintQueueIfNeeded(force = false) { const queue = loadMintQueue(); if (!queue || !queue.active) return false; if (STATE.running && !force) return true; STATE.running = true; try { await ensureMyTasksPage(); await expandAllSections(); await sleep(1000); if (await handleMintSuccessIfNeeded()) { return true; } const liveTitles = new Set(loadMintableTasksFromDom().map((task) => task.title)); if (queue.currentTitle && !liveTitles.has(queue.currentTitle)) { log(`检测到已完成: ${queue.currentTitle}`); queue.index += 1; queue.currentTitle = null; saveMintQueue(queue); } if (queue.index >= queue.tasks.length) { log(`待铸造流程结束,已触发 ${queue.tasks.length}/${queue.tasks.length} 个任务`); clearMintQueue(); return true; } let nextTitle = queue.tasks[queue.index]; while (nextTitle && !findCardEntryByTitle(nextTitle)) { log(`跳过已不在待铸造列表的任务: ${nextTitle}`); queue.index += 1; queue.currentTitle = null; saveMintQueue(queue); if (queue.index >= queue.tasks.length) { log(`待铸造流程结束,已处理 ${queue.tasks.length}/${queue.tasks.length} 个任务`); clearMintQueue(); return true; } nextTitle = queue.tasks[queue.index]; } const nextTask = { title: nextTitle }; log(`恢复批量铸造,继续处理 ${queue.index + 1}/${queue.tasks.length}: ${nextTitle}`); queue.currentTitle = nextTitle; saveMintQueue(queue); const ok = await triggerMintForTask(nextTask, queue.index + 1, queue.tasks.length); if (ok) { saveMintQueue(queue); } else { queue.currentTitle = null; saveMintQueue(queue); } return true; } catch (error) { console.error(error); log(`恢复铸造流程异常: ${error.message || error}`); return false; } finally { STATE.running = false; } } async function expandAllSections() { const buttons = [...document.querySelectorAll("button")].filter((button) => normalize(button.textContent).includes("显示全部") ); for (const button of buttons) { button.click(); await sleep(250); } } async function assistMint() { if (STATE.running) return; STATE.running = true; STATE.stopRequested = false; try { log("开始拉取待铸造任务列表"); let tasks = []; let payload = null; try { const result = await loadMintableTasks(); payload = result.payload; tasks = result.tasks; } catch (error) { log(`接口识别失败,转 DOM 兜底: ${error.message || error}`); } if (tasks.length === 0) { await ensureMyTasksPage(); await expandAllSections(); await sleep(800); tasks = loadMintableTasksFromDom(); } if (tasks.length === 0) { log("接口和 DOM 都没找到待铸造任务"); if (payload) console.debug("[MoledaoHelper] mint /my-tasks payload", payload); return; } log(`找到 ${tasks.length} 个待铸造任务`); const preview = tasks .slice(0, 5) .map((task) => task.title) .join(" / "); log(`前几个任务: ${preview}${tasks.length > 5 ? " ..." : ""}`); await ensureMyTasksPage(); await expandAllSections(); await sleep(800); const queue = { active: true, index: 0, currentTitle: null, tasks: tasks.map((task) => task.title), }; saveMintQueue(queue); log("已保存铸造队列,页面闪退后刷新也会继续"); STATE.running = false; await resumeMintQueueIfNeeded(true); return; } catch (error) { console.error(error); log(`铸造流程异常: ${error.message || error}`); } finally { STATE.running = false; } } function bindPress(el, handler) { el.addEventListener("pointerdown", async (event) => { event.preventDefault(); event.stopPropagation(); flashPanel(); try { await handler(event); } catch (error) { console.error(error); log(`按钮执行失败: ${error.message || error}`); } }); } function savePanelPosition(panel) { const rect = panel.getBoundingClientRect(); localStorage.setItem(POS_KEY, JSON.stringify({ left: rect.left, top: rect.top })); } function restorePanelPosition(panel) { const raw = localStorage.getItem(POS_KEY); if (!raw) return; try { const pos = JSON.parse(raw); if (typeof pos.left === "number" && typeof pos.top === "number") { panel.style.left = `${Math.max(8, pos.left)}px`; panel.style.top = `${Math.max(8, pos.top)}px`; panel.style.right = "auto"; panel.style.bottom = "auto"; } } catch (_) { // ignore } } function setPanelCollapsed(panel, collapsed) { panel.classList.toggle("mh-collapsed", collapsed); localStorage.setItem(COLLAPSED_KEY, collapsed ? "1" : "0"); const toggle = panel.querySelector(".mh-toggle"); if (toggle) { toggle.textContent = collapsed ? "展开" : "收起"; toggle.title = collapsed ? "展开面板" : "收起面板"; } } function restorePanelCollapsed(panel) { setPanelCollapsed(panel, localStorage.getItem(COLLAPSED_KEY) === "1"); } function makePanelDraggable(panel, handle) { let startX = 0; let startY = 0; let startLeft = 0; let startTop = 0; let dragging = false; const onMove = (event) => { if (!dragging) return; panel.style.left = `${Math.max(8, startLeft + (event.clientX - startX))}px`; panel.style.top = `${Math.max(8, startTop + (event.clientY - startY))}px`; panel.style.right = "auto"; panel.style.bottom = "auto"; }; const onUp = () => { if (!dragging) return; dragging = false; savePanelPosition(panel); window.removeEventListener("mousemove", onMove); window.removeEventListener("mouseup", onUp); }; handle.addEventListener("mousedown", (event) => { dragging = true; const rect = panel.getBoundingClientRect(); startX = event.clientX; startY = event.clientY; startLeft = rect.left; startTop = rect.top; window.addEventListener("mousemove", onMove); window.addEventListener("mouseup", onUp); }); } function buildPanel() { if (document.getElementById(PANEL_ID)) return; const panel = document.createElement("div"); panel.id = PANEL_ID; panel.innerHTML = `
Moledao Helper v${VERSION}
待命中
`; const style = document.createElement("style"); style.textContent = ` #${PANEL_ID} { position: fixed; right: 20px; bottom: 20px; z-index: 999999; width: 210px; padding: 12px; border-radius: 16px; background: rgba(22, 24, 35, 0.92); color: #fff; box-shadow: 0 16px 40px rgba(0, 0, 0, 0.28); backdrop-filter: blur(12px); font: 13px/1.4 -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; border: 2px solid transparent; pointer-events: auto; } #${PANEL_ID}.mh-collapsed { width: auto; min-width: 136px; padding: 8px 10px; border-radius: 999px; } #${PANEL_ID}.mh-flash { border-color: #ffd54f; } #${PANEL_ID} .mh-title { display: flex; align-items: center; justify-content: space-between; gap: 8px; font-weight: 700; margin-bottom: 8px; cursor: move; user-select: none; } #${PANEL_ID}.mh-collapsed .mh-title { margin-bottom: 0; } #${PANEL_ID} .mh-version { opacity: 0.7; font-size: 11px; font-weight: 500; } #${PANEL_ID} .mh-toggle { border: 0; border-radius: 999px; padding: 3px 7px; cursor: pointer; color: #10131a; background: #e6edf7; font-size: 11px; font-weight: 700; } #${PANEL_ID} .mh-content { display: block; } #${PANEL_ID}.mh-collapsed .mh-content { display: none; } #${PANEL_ID} .mh-status { margin-bottom: 8px; color: #ffd54f; font-size: 12px; min-height: 16px; word-break: break-word; } #${PANEL_ID} .mh-btn { width: 100%; border: 0; border-radius: 10px; padding: 8px 10px; margin: 0 0 8px; cursor: pointer; color: #10131a; background: #ffd54f; font-weight: 600; } #${PANEL_ID} .mh-btn.mh-mint { background: #80deea; } #${PANEL_ID} .mh-btn.mh-stop { background: #ef9a9a; } #${PANEL_ID} .mh-btn:active { transform: scale(0.98); } #${PANEL_ID} .mh-log { min-height: 52px; max-height: 180px; overflow: auto; color: #d7dde8; font-size: 12px; } #${PANEL_ID} .mh-log-line { margin-bottom: 4px; word-break: break-word; } `; document.head.appendChild(style); document.body.appendChild(panel); bindPress(panel.querySelector(".mh-claim"), claimViaApi); bindPress(panel.querySelector(".mh-mint"), assistMint); bindPress(panel.querySelector(".mh-toggle"), () => { setPanelCollapsed(panel, !panel.classList.contains("mh-collapsed")); }); bindPress(panel.querySelector(".mh-stop"), () => { STATE.stopRequested = true; clearMintQueue(); log("停止请求已发送"); }); makePanelDraggable(panel, panel.querySelector(".mh-title")); restorePanelPosition(panel); restorePanelCollapsed(panel); } function boot() { buildPanel(); log(`已加载 v${VERSION}`); if (!STATE.mintWatcherStarted) { STATE.mintWatcherStarted = true; setInterval(() => { handleMintSuccessIfNeeded().catch((error) => { console.error(error); }); }, 2000); } setTimeout(() => { resumeMintQueueIfNeeded(); }, 1200); } if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", boot, { once: true }); } else { boot(); } })();