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