【十三周年】【异界方块】【脚本】金币来自动使用
本帖最后由 Brine 于 2026-6-10 17:31 编辑static/image/hrline/5.gif
前情提要
你是否因为忘记上次金币卡的使用时间导致想用道具发现还在冷却,等到下次想起时,时间已经延后,一天一天逐渐累积导致下周到了还有没用掉的金币卡?
或者你只是单纯不想检查自己还有没有可用的金币卡,想着有没有自动使用的方式
如果以上问题困扰着你,那么这个脚本就是来帮助你的
static/image/hrline/5.gif
感谢好朋友@是阿行嘞 的不断投喂{:6_169:}在安装脚本后,登入泥潭时会静默检查,检测到有道具和使用次数便会自动帮你使用,全程无感// ==UserScript==
// @Name 金币来自动使用
// @namespace https://www.gamemale.com/
// @version 0.3.0
// @description自动使用“金币来”道具,滚动 7 天内使用未满 3 次则自动使用到上限。
// @author brine
// @Match https://www.gamemale.com/*
// @run-at document-idle
// @grant none
// ==/UserScript==
(function () {
"use strict";
const CONFIG = {
magicName: "金币来",
weeklyLimit: 3,
rollingWindowMs: 7 * 24 * 60 * 60 * 1000,
checkIntervalMs: 6 * 60 * 60 * 1000,
lockMs: 2 * 60 * 1000,
requestTimeoutMs: 15000,
submitGapMs: 1400,
unlockJitterMs: 2 * 60 * 1000,
debug: false,
};
const BOX_URL = "/home.php?mod=magic&action=mybox";
const USE_LOG_URL = "/home.php?mod=magic&action=log&operation=uselog";
const STATE_KEY = "gm_jinbilai_auto_use_state_v3";
const LOCK_KEY = "gm_jinbilai_auto_use_lock_v1";
if (window.top !== window.self) return;
if (location.hostname !== "www.gamemale.com") return;
const log = (...args) => {
if (CONFIG.debug) console.info("", ...args);
};
const now = () => Date.now();
const readJSON = (key, fallback) => {
try {
const raw = localStorage.getItem(key);
return raw ? JSON.parse(raw) : fallback;
} catch (_) {
return fallback;
}
};
const writeJSON = (key, value) => {
try {
localStorage.setItem(key, JSON.stringify(value));
} catch (_) {
// Ignore storage failures; server-side checks still protect the action.
}
};
const absoluteURL = (url) => new URL(url, location.origin).href;
const fetchText = async (url, options = {}) => {
const controller = new AbortController();
const timer = window.setTimeout(
() => controller.abort(),
CONFIG.requestTimeoutMs,
);
try {
const response = await fetch(absoluteURL(url), {
credentials: "same-origin",
redirect: "follow",
signal: controller.signal,
...options,
headers: {
"X-Requested-With": "XMLHttpRequest",
...(options.headers || {}),
},
});
return await response.text();
} finally {
window.clearTimeout(timer);
}
};
const parseHTML = (html) =>
new DOMParser().parseFromString(html, "text/html");
const sleep = (ms) =>
new Promise((resolve) => window.setTimeout(resolve, ms));
const isLoggedOutPage = (doc) => {
const text = doc.body ? doc.body.textContent || "" : "";
return /尚未登录|请先登录|not_loggedin|member\.php\?mod=logging/.test(text);
};
const acquireLock = () => {
const token = `${now()}-${Math.random().toString(36).slice(2)}`;
const current = readJSON(LOCK_KEY, null);
if (current && current.expiresAt && current.expiresAt > now()) return null;
writeJSON(LOCK_KEY, { token, expiresAt: now() + CONFIG.lockMs });
const stored = readJSON(LOCK_KEY, null);
return stored && stored.token === token ? token : null;
};
const releaseLock = (token) => {
const current = readJSON(LOCK_KEY, null);
if (current && current.token === token) localStorage.removeItem(LOCK_KEY);
};
const shouldCheck = () => {
const state = readJSON(STATE_KEY, {});
if (state.nextCheckAt && state.nextCheckAt > now()) return false;
return true;
};
const scheduleNextCheck = (
extra = {},
nextCheckAt = now() + CONFIG.checkIntervalMs,
) => {
writeJSON(STATE_KEY, {
...readJSON(STATE_KEY, {}),
...extra,
nextCheckAt,
checkedAt: now(),
});
};
const findMagicItem = (doc) => {
const candidates = Array.from(
doc.querySelectorAll("li, tr, .bm, .xld, .mgcl > *"),
);
for (const node of candidates) {
const text = node.textContent || "";
if (!text.includes(CONFIG.magicName)) continue;
const useLink = Array.from(node.querySelectorAll("a")).find(
(anchor) => {
const href = anchor.getAttribute("href") || "";
const label = anchor.textContent || "";
return (
href.includes("mod=magic") &&
href.includes("action=mybox") &&
href.includes("operation=use") &&
/使用|use/i.test(label + href)
);
},
);
const source = useLink ? useLink.href : node.innerHTML;
const magicId = extractMagicId(source);
const count = extractOwnedCount(text);
return { magicId, useHref: useLink ? useLink.href : "", count, text };
}
const byAlt = Array.from(
doc.querySelectorAll(`img`),
);
for (const img of byAlt) {
const node = img.closest("li, tr, .bm, .xld, .mgcl > *");
if (!node) continue;
const link = node.querySelector(
'a',
);
return {
magicId: extractMagicId(link ? link.href : node.innerHTML),
useHref: link ? link.href : "",
count: extractOwnedCount(node.textContent || ""),
text: node.textContent || "",
};
}
return null;
};
const extractMagicId = (source) => {
const decoded = String(source || "").replace(/&/g, "&");
const match =
decoded.match(/[?&]magicid=(\d+)/i) ||
decoded.match(/magicid['"]?\s*[:=]\s*['"]?(\d+)/i);
return match ? match : "";
};
const extractOwnedCount = (text) => {
const normalized = String(text || "").replace(/\s+/g, " ");
const match = normalized.match(/(?:数量|数目|num)\D{0,8}(\d+)/i);
return match ? Number(match) : null;
};
const parseDateCell = (text) => {
const trimmed = String(text || "").trim();
const normalized = trimmed.replace(/\s+/g, " ");
const nowDate = new Date();
let match = normalized.match(
/(\d{4})[-/年.](\d{1,2})[-/月.](\d{1,2})日?\s*(\d{1,2}):(\d{2})(?::(\d{2}))?/,
);
if (match) {
return new Date(
Number(match),
Number(match) - 1,
Number(match),
Number(match),
Number(match),
Number(match || 0),
);
}
match = normalized.match(
/(\d{1,2})[-/月.](\d{1,2})日?\s*(\d{1,2}):(\d{2})(?::(\d{2}))?/,
);
if (match) {
return new Date(
nowDate.getFullYear(),
Number(match) - 1,
Number(match),
Number(match),
Number(match),
Number(match || 0),
);
}
match = normalized.match(/昨天\s*(\d{1,2}):(\d{2})(?::(\d{2}))?/);
if (match) {
const date = new Date(
nowDate.getFullYear(),
nowDate.getMonth(),
nowDate.getDate() - 1,
);
date.setHours(
Number(match),
Number(match),
Number(match || 0),
0,
);
return date;
}
match = normalized.match(/今天\s*(\d{1,2}):(\d{2})(?::(\d{2}))?/);
if (match) {
const date = new Date(
nowDate.getFullYear(),
nowDate.getMonth(),
nowDate.getDate(),
);
date.setHours(
Number(match),
Number(match),
Number(match || 0),
0,
);
return date;
}
return null;
};
const getRecentUses = (doc) => {
const windowStart = new Date(now() - CONFIG.rollingWindowMs);
const rows = Array.from(doc.querySelectorAll("table.dt tr, table tr"));
const dates = [];
for (const row of rows) {
const text = row.textContent || "";
if (!text.includes(CONFIG.magicName)) continue;
const cells = Array.from(row.querySelectorAll("td, th"));
const date =
cells.map((cell) => parseDateCell(cell.textContent)).find(Boolean) ||
parseDateCell(text);
if (date && date >= windowStart) dates.push(date);
}
return dates.sort((left, right) => left.getTime() - right.getTime());
};
const getNextRollingSlotAt = (recentUses, addedUses = 0) => {
const dates = [...recentUses];
for (let index = 0; index < addedUses; index += 1) {
dates.push(new Date(now() + index * CONFIG.submitGapMs));
}
dates.sort((left, right) => left.getTime() - right.getTime());
if (dates.length < CONFIG.weeklyLimit) return now() + CONFIG.checkIntervalMs;
return dates.getTime() + CONFIG.rollingWindowMs + CONFIG.unlockJitterMs;
};
const getFormHash = (doc) => {
const input = doc.querySelector('input');
if (input && input.value) return input.value;
const html = doc.documentElement ? doc.documentElement.innerHTML : "";
const match = html.match(/formhash[=:]['"]?({8,})/i);
return match ? match : "";
};
const getUseForm = async (magicId, useHref) => {
const href =
useHref ||
`${BOX_URL}&operation=use&magicid=${encodeURIComponent(magicId)}`;
const html = await fetchText(href);
const doc = parseHTML(html);
if (isLoggedOutPage(doc)) return null;
const form = doc.querySelector('form#magicform, form');
const formHash = getFormHash(doc);
if (!formHash) return null;
const action = form
? form.getAttribute("action") || `${BOX_URL}&infloat=yes`
: `${BOX_URL}&infloat=yes`;
const data = new URLSearchParams();
if (form) {
for (const field of Array.from(
form.querySelectorAll("input, select, textarea"),
)) {
if (!field.name || field.disabled) continue;
if (
(field.type === "checkbox" || field.type === "radio") &&
!field.checked
)
continue;
data.set(field.name, field.value || "");
}
}
data.set("formhash", formHash);
data.set("handlekey", data.get("handlekey") || "magics");
data.set("operation", "use");
data.set("magicid", magicId);
data.set("usesubmit", "true");
return { action, data };
};
const submitUse = async (magicId, useHref) => {
const useForm = await getUseForm(magicId, useHref);
if (!useForm) return { ok: false, reason: "missing-use-form" };
const html = await fetchText(useForm.action, {
method: "POST",
body: useForm.data,
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
});
const success =
/succeedhandle|使用|成功|showDialog|showCreditPrompt/i.test(html) &&
!/outofperoid|超过|已达到|上限|nopermission|失败|错误/i.test(html);
return { ok: success, reason: success ? "used" : "server-rejected", html };
};
const useUntilLimit = async (magic, recentUses) => {
const remainingWeeklyUses = Math.max(
0,
CONFIG.weeklyLimit - recentUses.length,
);
const ownedLimit = Number.isFinite(magic.count)
? Math.max(0, magic.count)
: remainingWeeklyUses;
const targetUses = Math.min(remainingWeeklyUses, ownedLimit);
let successCount = 0;
let lastResult = { ok: false, reason: "nothing-to-use" };
for (let index = 0; index < targetUses; index += 1) {
if (index > 0) await sleep(CONFIG.submitGapMs);
lastResult = await submitUse(magic.magicId, magic.useHref);
if (!lastResult.ok) break;
successCount += 1;
}
return {
...lastResult,
successCount,
targetUses,
reason:
successCount === targetUses
? "used-to-limit"
: successCount > 0
? "partial-used"
: lastResult.reason,
};
};
const run = async () => {
if (!shouldCheck()) return;
const token = acquireLock();
if (!token) return;
try {
const boxDoc = parseHTML(await fetchText(BOX_URL));
if (isLoggedOutPage(boxDoc)) {
scheduleNextCheck({ lastResult: "not-logged-in" });
return;
}
const magic = findMagicItem(boxDoc);
if (!magic || !magic.magicId || magic.count === 0) {
scheduleNextCheck({ lastResult: "no-magic" });
return;
}
const logDoc = parseHTML(await fetchText(USE_LOG_URL));
if (isLoggedOutPage(logDoc)) {
scheduleNextCheck({ lastResult: "not-logged-in" });
return;
}
const recentUses = getRecentUses(logDoc);
if (recentUses.length >= CONFIG.weeklyLimit) {
scheduleNextCheck(
{
lastResult: "rolling-limit",
usedInRollingWindow: recentUses.length,
},
getNextRollingSlotAt(recentUses),
);
return;
}
const result = await useUntilLimit(magic, recentUses);
scheduleNextCheck(
{
lastResult: result.reason,
usedCount: result.successCount,
targetUses: result.targetUses,
lastUseAt:
result.successCount > 0 ? now() : readJSON(STATE_KEY, {}).lastUseAt,
usedInRollingWindowBeforeUse: recentUses.length,
},
getNextRollingSlotAt(recentUses, result.successCount),
);
log(result.reason);
} catch (error) {
log(error);
scheduleNextCheck({
lastResult: "error",
lastError: String(error && error.message ? error.message : error),
});
} finally {
releaseLock(token);
}
};
window.setTimeout(run, 3000 + Math.floor(Math.random() * 5000));
})();
static/image/hrline/5.gif
{:6_184:}食用愉快{:6_184:}
完全没有这个烦恼
XS,因为我根本抢不到金币卡和补签卡
不过脚本确实是好东西
那么有没有可能放进免费的华为云函数里让他每天滚动使用一遍,这样都不需要登录了呢【开始胡思乱想 金币卡自动使用还挺方便的惹,再也不用担心会忘记金币卡的存在了{:6_169:} 这种跟时间有关的总是会让人焦虑啊 完全没有用过金币卡呢,为啥呀,因为根本抢不到hhhhhh 金币卡用了几次都是30几,血亏。补签卡根本抢不到。:'( https://img.gamemale.com/album/202509/04/083047t3sjzml6szil4o26.gif这下会自动使用金币卡了,不用担心忘记使用了 看了帖子发现自己还有张金币卡,小赚19金~ 啊no撒,你这不是在剥夺我自由开卡的乐趣和权利吗?我在百忙之中抽出时间开卡可是十分神圣的举动!(雷古勒斯如是说到{:6_189:} 很方便的脚本呢,虽然懒人根本就抢不到金币卡就是了:'( https://i.111666.best/image/QXzlxVGCj0cTlBq15tx20I.jpg
伯糕当然是要每周投喂的啦,好吃的伯糕w
脚本已收入囊中,給自己的分身直接使用一个一键使用{:6_179:} 金币卡是一场豪赌,不过有了自动使用,这样每周蹲点抢更方便了。 本人道具完全没有空位了,金币卡肯定是用不上的 https://img.gamemale.com/album/202508/27/154618wt52fzit25qb9aaf.jpg其实来泥潭都几年了 我还没去抢过金币卡和补签卡{:6_165:} 原来还有这种烦恼吗,我经常忘记要抢惹,只能安慰自己睡眠更重要,原来还有冷却惹,看描述有多个金币卡的话确实很方便惹,适合我这种容易忘记的人。 商人弗霖打开异界的百宝袋,袋口微光一闪,一枚【橙色方块】轻巧地蹦了出来。 完全抢不到惹,甚至连金币卡具体功能我都不太清楚 金币卡也要赌吗?还以为是纯福利来着 完全用不到啊_(:3」∠)_,不过对于经常买的人肯定很有用 上次使用金币卡都忘记是什么时候了,但还是感谢分享~~
页:
[1]
2