您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Show tornstats spies on faction wall page
当前为
// ==UserScript== // @name Faction Wall Battlestats // @description Show tornstats spies on faction wall page // @namespace finally.torn.FactionWallBattlestats // @match https://www.torn.com/factions.php* // @run-at document-end // @grant GM_addStyle // @grant GM_xmlhttpRequest // @version 1.11 // @author finally // ==/UserScript== let apiKey = localStorage["finally.torn.api"] || null; let bsCache = JSONparse(localStorage["finally.torn.bs"]) || {}; GM_addStyle(` @media screen and (max-width: 1000px) { .members-cont .bs { display: none; } } .members-cont .level { width: 27px !important; } .members-cont .id { padding-left: 5px !important; width: 28px !important; } .members-cont .points { width: 42px !important; } .finally-bs-stat { font-family: monospace; } .finally-bs-stat > span { display: inline-block; width: 55px; text-align: right; } .faction-names { position: relative; } .finally-bs-api { position: absolute; background: var(--main-bg); text-align: center; left: 0; top: 0; width: 100%; height: 100%; } .finally-bs-api > * { margin: 0 5px; padding: 5px; } .finally-bs-swap { position: absolute; top: 10px; left: 0; right: 0; margin-left: auto; margin-right: auto; width: 100px; cursor: pointer; } `); function JSONparse(str) { try { return JSON.parse(str); } catch (e) {} return null; } function checkApiKey(key, cb) { GM_xmlhttpRequest({ method: "GET", url: `https://www.tornstats.com/api/v1/${key}`, onload: (r) => { if (r.status == 429){ cb("Couldn't check (rate limit)"); return; } if (r.status != 200){ cb(`Couldn't check (status code ${r.status})`); return; } let j = JSONparse(r.responseText); if (!j){ cb("Couldn't check (unexpected response)"); return; } if (!j.status) { cb("Wrong API key?"); } else { apiKey = key; localStorage["finally.torn.api"] = key; cb(true); } }, onabort: () => cb("Couldn't check (aborted)"), onerror: () => cb("Couldn't check (error)"), ontimeout: () => cb("Couldn't check (timeout)") }) } let apiKeyCheck = false; function addAPIKeyInput(node) { if (!node) return; node.style.position = "relative"; let apiKeyNode = document.createElement("div"); apiKeyNode.className = "text faction-names finally-bs-api"; apiKeyNode.style.display = (!apiKey) ? "block" : "none"; let apiKeyText = document.createElement("span"); apiKeyText.innerHTML = ((!apiKey) ? "Set" : "Update") + " your API key: "; let apiKeyInput = document.createElement("input"); let apiKeySave = document.createElement("input"); apiKeySave.type = "button"; apiKeySave.value = "Save"; let apiKeyClose = document.createElement("input"); apiKeyClose.type = "button"; apiKeyClose.value = "Close"; let wipeButton = document.createElement("input"); wipeButton.type = "button"; wipeButton.value = "Wipe & Reload"; apiKeyNode.appendChild(apiKeyText); apiKeyNode.appendChild(apiKeyInput); apiKeyNode.appendChild(apiKeySave); apiKeyNode.appendChild(apiKeyClose); apiKeyNode.appendChild(wipeButton); function checkApiKeyCb(r) { if (r === true) { apiKeyNode.style.display = "none"; apiKeyInput.value = ""; showStatsAll(); } else { apiKeyNode.style.display = "block"; apiKeyText.innerHTML = `${r}: `; } } apiKeySave.addEventListener("click", () => { apiKeyText.innerHTML = "Checking key"; checkApiKey(apiKeyInput.value, checkApiKeyCb); }); apiKeyClose.addEventListener("click", () => { apiKeyNode.style.display = "none"; }); wipeButton.addEventListener("click", () => { bsCache = {}; localStorage["finally.torn.bs"] = JSON.stringify(bsCache); apiKeyNode.style.display = "none"; window.location.reload(); }); let apiKeyButton = document.createElement("a"); apiKeyButton.className = "t-clear h c-pointer line-h24 right "; apiKeyButton.innerHTML = ` <span>Update API Key</span> `; apiKeyButton.addEventListener("click", () => { apiKeyText.innerHTML = "Update your API key: "; apiKeyNode.style.display = "block"; }); node.querySelector("#top-page-links-list").appendChild(apiKeyButton); node.appendChild(apiKeyNode); if (apiKey && !apiKeyCheck) { apiKeyCheck = true; checkApiKey(apiKey, checkApiKeyCb); } } function sortStats(node) { if (!node) node = document.querySelector(".f-war-list .members-list"); if (!node) return; if (node.finallySort == undefined) node.finallySort = 0; if (++node.finallySort > 2) node.finallySort = 0; let nodes = Array.from(node.querySelectorAll(".table-body > .table-row, .your:not(.row-animation-new), .enemy:not(.row-animation-new)")); for (let i = 0; i < nodes.length; i++) if (nodes[i].finallyPos == undefined) nodes[i].finallyPos = i; nodes = nodes.sort((a,b) => { let posA = a.finallyPos; let idA = a.querySelector('a[href*="XID"]').href.replace(/.*?XID=(\d+)/i, "$1"); let totalA = (bsCache[idA] && typeof bsCache[idA].total == 'number' && bsCache[idA].total) || posA; let posB = b.finallyPos; let idB = b.querySelector('a[href*="XID"]').href.replace(/.*?XID=(\d+)/i, "$1"); let totalB = (bsCache[idB] && typeof bsCache[idB].total == 'number' && bsCache[idB].total) || posB; let type = node.finallySort; switch(node.finallySort) { case 1: if (totalA <= 100) return 1; else if (totalB <= 100) return -1; return totalA > totalB ? 1 : -1; case 2: return totalB > totalA ? 1 : -1; default: return posA > posB ? 1 : -1; } }); for (let i = 0; i < nodes.length; i++) nodes[i].parentNode.appendChild(nodes[i]); } let loadStatsLock = false; let loadStatsBacklog = []; function loadStats(id, node) { if (!apiKey) return; if (loadStatsLock) { if (id) loadStatsBacklog.push([id, node]); return; } loadStatsLock = true; if (!id) { if (loadStatsBacklog.length == 0) { loadStatsLock = false; return; } else { let backlog = loadStatsBacklog.shift(); id = backlog[0]; node = backlog[1]; } } function loadStatsDone(t = 0){ setTimeout(() => { loadStatsLock = false; loadStats(); }, t); } GM_xmlhttpRequest({ method: "GET", url: `https://www.tornstats.com/api/v1/${apiKey}/spy/${id}`, onload: (r) => { if (r.status == 429) { let headers = r.responseHeaders.split("\r\n").map((h) => h.split(":")); let retry = 60; headers.forEach((h) => { if (h[0].toLowerCase() != "retry-after") return; retry = ~~h[1]; }); loadStatsDone(retry*1000); return; } loadStatsDone(); let j = JSONparse(r.responseText); if (!j || !j.spy || !j.spy.status) return; bsCache[id] = j.spy; bsCache[id].timestamp = new Date().getTime(); localStorage["finally.torn.bs"] = JSON.stringify(bsCache); updateStats(id, node); }, onabort: () => loadStatsDone(), onerror: () => loadStatsDone(), ontimeout: () => loadStatsDone() }); } function updateStats(id, node) { if (!node) return; let stats = ["N/A", "N/A", "N/A", "N/A", "N/A"]; let time = ""; if (bsCache[id]) { stats[0] = bsCache[id].total; stats[1] = bsCache[id].strength; stats[2] = bsCache[id].defense; stats[3] = bsCache[id].speed; stats[4] = bsCache[id].dexterity; time = bsCache[id].difference; } let units = ["K", "M", "B", "T", "Q"] for(let i = 0; i < stats.length; i++) { let stat = Number.parseInt(stats[i]); if (Number.isNaN(stat) || stat == 0) continue; for (let j = 0; j < units.length; j++) { stat = stat / 1000; if (stat > 1000) continue; stat = stat.toFixed(i == 0 ? (stat >= 100 ? 0 : 1) : 2); stats[i] = `${stat}${units[j]}`; break; } } node.innerHTML = stats[0]; node.title = ` <div class="finally-bs-stat"> <b>STR</b> <span class="finally-bs-stat">${stats[1]}</span><br/> <b>DEF</b> <span class="finally-bs-stat">${stats[2]}</span><br/> <b>SPD</b> <span class="finally-bs-stat">${stats[3]}</span><br/> <b>DEX</b> <span class="finally-bs-stat">${stats[4]}</span><br/> ${time} </div>`; } function showStats(node) { if (!node) return; let id = node.querySelector('a[href*="XID"]').href.replace(/.*?XID=(\d+)/i, "$1"); let bsNode = node.querySelector(".bs") || document.createElement("div"); if (bsNode.classList.contains("bs")) { loadStats(id, bsNode); return; } if (!bsCache[id] || bsCache[id].timestamp <= (new Date()-(24*60*60*1000))) { loadStats(id, bsNode); } updateStats(id, bsNode); bsNode.className = "table-cell bs level lvl left iconShow"; let iconsNode = node.querySelector(".user-icons, .member-icons, .points"); iconsNode.parentNode.insertBefore(bsNode, iconsNode); } function showStatsAll(node) { if (!node) node = document.querySelector(".f-war-list .members-list"); if (!node) return; node.querySelectorAll(".your:not(.row-animation-new), .enemy:not(.row-animation-new)").forEach((e) => showStats(e)); } function watchWall(observeNode) { if (!observeNode) return; let parentNode = observeNode.parentNode.parentNode.parentNode; let factionNames = parentNode.querySelector(".faction-names"); if (!factionNames.querySelector(".finally-bs-swap")) { let swapNode = document.createElement("div"); swapNode.className = "finally-bs-swap"; swapNode.innerHTML = "<>"; factionNames.appendChild(swapNode); swapNode.addEventListener("click", () => { parentNode.querySelectorAll(".name.left, .name.right, .tab-menu-cont.right, .tab-menu-cont.left").forEach((e) => { if (e.classList.contains("left")) { e.classList.remove("left"); e.classList.add("right"); } else { e.classList.remove("right"); e.classList.add("left"); } }) }); } let titleNode = observeNode.parentNode.querySelector(".title, .c-pointer"); titleNode.querySelector(".level").innerText = "Lv"; if (!titleNode.querySelector(".bs")) { let bsNode = document.createElement("div"); bsNode.className = "bs level left"; bsNode.innerHTML = "BS"; titleNode.insertBefore(bsNode, titleNode.querySelector(".user-icons, .points")); bsNode.addEventListener("click", () => { sortStats(observeNode); }); } showStatsAll(observeNode); new MutationObserver(mutations => { mutations.forEach(mutation => { for (const node of mutation.addedNodes) { if (node.classList && (node.classList.contains("your") || node.classList.contains("enemy"))) { showStats(node); } } }); }).observe(observeNode, {childList: true, subtree: true}); } function watchWalls(observeNode) { if (!observeNode) return; observeNode.querySelectorAll(".members-list").forEach((e) => watchWall(e)); new MutationObserver(mutations => { mutations.forEach(mutation => { for (const node of mutation.addedNodes) { node.querySelector && node.querySelectorAll(".members-list").forEach((w) => watchWall(w)); } }); }).observe(observeNode, {childList: true, subtree: true}); } function memberList(observeNode) { if (!observeNode) return; let titleNode = observeNode.querySelector(".table-header"); if (!titleNode || titleNode.querySelector(".bs")) return; let bsNode = document.createElement("li"); bsNode.className = "table-cell bs torn-divider divider-vertical"; bsNode.innerHTML = "BS"; titleNode.insertBefore(bsNode, titleNode.querySelector(".member-icons")); bsNode.addEventListener("click", () => { sortStats(observeNode); }); observeNode.querySelectorAll(".table-body > .table-row").forEach((e) => showStats(e)); } memberList(document.querySelector(".members-list")); watchWalls(document.querySelector(".f-war-list")); addAPIKeyInput(document.querySelector(".content-title")); new MutationObserver(mutations => { mutations.forEach(mutation => { for (const node of mutation.addedNodes) { memberList(node.querySelector && node.querySelector(".members-list")); watchWalls(node.querySelector && node.querySelector(".f-war-list")); addAPIKeyInput(node.querySelector && node.querySelector(".content-title")); } }); }).observe(document.body, {childList: true, subtree: true});
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址