您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Track crime chain bonus from Torn logs & webpages realtime
// ==UserScript== // @name Torn Crime Chain Bonus Tracker // @namespace http://tampermonkey.net/ // @author kaeru [1769499] // @version 1.0 // @description Track crime chain bonus from Torn logs & webpages realtime // @match https://www.torn.com/loader.php?sid=crimes* // @grant none // @license MIT // ==/UserScript== /* jshint esversion: 8 */ (function () { 'use strict'; const API_KEY = ''; // Replace with your Torn API key (Full Access) const STORAGE_TS_KEY = 'torn_chain_last_ts'; const STORAGE_BONUS_KEY = 'torn_chain_last_bonus'; const STORAGE_RUN_KEY = 'torn_chain_last_run'; const LOG_LIMIT = 1000; const MIN_INTERVAL = 30 * 1000; // 30 seconds let lastTimestamp = parseInt(localStorage.getItem(STORAGE_TS_KEY)) || 0; let lastBonus = parseFloat(localStorage.getItem(STORAGE_BONUS_KEY)) || 0; let lastDomSuccesses = null; let lastDomFails = null; let lastDomCriticalFails = null; let errorMessage = null; function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } async function fetchLogsBackward(toTimestamp) { let currentTo = toTimestamp; const allLogs = []; if (!API_KEY) { errorMessage = "No API Key"; return []; } while (true) { const url = `https://api.torn.com/user/?selections=log&cat=136&to=${currentTo}&key=${API_KEY}`; const res = await fetch(url); const data = await res.json(); if (data.error) { console.error(`[CrimeChainBonus] API Error: ${data.error.code} - ${data.error.error}`); errorMessage = data.error.error; break; } else { errorMessage = null; } if (!data || !data.log) break; const logs = Object.entries(data.log) .map(([ts, entry]) => ({ timestamp: parseInt(ts), ...entry })) .sort((a, b) => b.timestamp - a.timestamp); if (logs.length === 0) break; if (allLogs.length >= LOG_LIMIT) break; for (const log of logs) { if (log.timestamp <= lastTimestamp) { return allLogs; // Stop fetching if we've reached known timestamp } if (log.title.toLowerCase().startsWith('crime critical fail')) { return allLogs; } allLogs.push(log); console.log(log); } currentTo = logs[logs.length - 1].timestamp; // Move backward using the oldest timestamp returned // Wait 1 second before next request await sleep(1000); } return allLogs; } function applyLogsIncrementally(baseBonus, logs) { let bonus = baseBonus; // Process logs from oldest to newest for (const log of logs.reverse()) { const title = (log.title || '').toLowerCase(); if (title.startsWith('crime success')) { bonus = bonus + 1; } else if (title.startsWith('crime fail')) { bonus = bonus / 2; } else if (title.startsWith('crime critical fail')) { bonus = 0; } } return bonus; } function displayBonus(bonus) { const container = document.querySelector('.crimes-app [class*="resultCounts"]'); if (!container) return; let el = container.querySelector('#chain-bonus-display'); if (!el) { el = document.createElement('div'); el.id = 'chain-bonus-display'; el.style.fontSize = '12px'; const label = document.createElement('span'); label.textContent = 'Crime Chain Bonus '; const value = document.createElement('span'); value.id = 'chain-bonus-value'; value.style.color = 'rgb(103, 140, 0)'; if (errorMessage) { value.textContent = errorMessage; } else { value.textContent = bonus.toFixed(2); } el.appendChild(label); el.appendChild(value); container.prepend(el); // Insert at the top } else { const value = el.querySelector('#chain-bonus-value'); if (errorMessage) { value.textContent = errorMessage; } else if (value) { value.textContent = bonus.toFixed(2); } else { el.textContent = `Crime Chain Bonus `; const span = document.createElement('span'); span.id = 'chain-bonus-value'; span.style.color = 'rgb(103, 140, 0)'; span.textContent = bonus.toFixed(2); el.appendChild(span); } } } async function updateBonus() { const now = Date.now(); const toTimestamp = Math.floor(now / 1000) + 10; const lastRun = parseInt(localStorage.getItem(STORAGE_RUN_KEY)) || 0; if (now - lastRun < MIN_INTERVAL) { console.log('[CrimeChainBonus] Skipping update (too soon)'); displayBonus(lastBonus); return; } const newLogs = await fetchLogsBackward(toTimestamp); if (newLogs.length === 0) { displayBonus(lastBonus); return; } const newTimestamp = newLogs[0].timestamp; const newBonus = applyLogsIncrementally(lastBonus, newLogs); // Store latest timestamp and bonus localStorage.setItem(STORAGE_TS_KEY, newTimestamp); localStorage.setItem(STORAGE_BONUS_KEY, newBonus); localStorage.setItem(STORAGE_RUN_KEY, now); // Update display displayBonus(newBonus); // Update current state lastTimestamp = newTimestamp; lastBonus = newBonus; } function parseCount(str) { return parseInt(str.replace(/,/g, ''), 10) || 0; } function readCrimeCounts() { const root = document.querySelector('.crimes-app'); if (!root) return { success: null, fail: null, critical: null }; const successEl = root.querySelector('[class*="resultCounts"] [class*="successes"] span'); const failEl = root.querySelector('[class*="resultCounts"] [class*="fails"] span'); const criticalEl = root.querySelector('[class*="resultCounts"] [class*="criticalFails"] span'); const parseOrNull = el => el ? parseCount(el.textContent) : null; return { successes: parseOrNull(successEl), fails: parseOrNull(failEl), criticalFails: parseOrNull(criticalEl), }; } function handleDomChangeByCounts() { const { successes, fails, criticalFails } = readCrimeCounts(); console.log(`Crime successes: ${successes}`); console.log(`Crime fails: ${fails}`); console.log(`Crime critialFails: ${criticalFails}`); const wasValid = lastDomSuccesses !== null && lastDomFails !== null && lastDomCriticalFails !== null; const isValid = successes !== null && fails !== null && criticalFails !== null; if (!wasValid || !isValid) { // Just track values, don't calculate bonus lastDomSuccesses = successes; lastDomFails = fails; lastDomCriticalFails = criticalFails; displayBonus(lastBonus); return; } const dSuccess = successes - lastDomSuccesses; const dFail = fails - lastDomFails; const dCritical = criticalFails - lastDomCriticalFails; if (dSuccess <= 0 && dFail <= 0 && dCritical <= 0) return; let bonus = lastBonus; if (dCritical > 0) { bonus = 0; } else if (dFail > 0) { bonus = Math.floor(bonus / Math.pow(2, dFail)); } if (dSuccess > 0) { bonus = bonus + dSuccess; } lastBonus = bonus; displayBonus(bonus); lastDomSuccesses = successes; lastDomFails = fails; lastDomCriticalFails = criticalFails; } // Initial call updateBonus(); // Observe DOM changes and trigger bonus update const observer = new MutationObserver(() => { handleDomChangeByCounts(); }); observer.observe(document.body, { childList: true, subtree: true }); })();