您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Captures crime outcome, skill gain and target data for analysis
当前为
// ==UserScript== // @name OutcomeDB // @namespace de.sabbasofa.outcomedb // @version 2.1.0 // @description Captures crime outcome, skill gain and target data for analysis // @author Hemicopter [2780600], Lazerpent [2112641] // @match https://www.torn.com/loader.php?sid=crimes* // @match https://torn.com/loader.php?sid=crimes* // @grant GM_xmlhttpRequest // @connect api.lzpt.io // ==/UserScript== (function () { 'use strict'; const isTampermonkeyEnabled = typeof unsafeWindow !== 'undefined'; const win = isTampermonkeyEnabled ? unsafeWindow : window; const {fetch: originalFetch} = win; let currentCrimesByTypeData; let serverTime = Math.floor(Date.now() / 1000); win.fetch = async (...args) => { let [resource, config] = args; return originalFetch(resource, config).then(response => detectCrimeDataRequest(resource, response)); }; registerCopyError(); console.log("[OutcomeDB] Watching for crime."); function detectCrimeDataRequest(resource, response) { if(!(resource.includes("sid=crimesData"))) return response; if (resource.includes("step=attempt")) response.clone().text().then(body => handleCrimeAttempt(body, resource)); if (resource.includes("step=crimesList")) response.clone().text().then(body => handleCrimesList(body, resource)); return response; } function handleCrimeAttempt(body, resource) { console.log("[OutcomeDB] Found crime attempt."); console.log("[OutcomeDB] url:", resource); //Most likely cloudflare turnstile or server error if(containsHtml(body)) { console.error("[OutcomeDB] Unexpected HTML data, skipping..."); return; } try { let data = JSON.parse(body); if (data.error) { console.log("[OutcomeDB] Failed crime attempt: " + data.error); console.log(JSON.stringify(data)); return; } if (!(data.DB && data.DB.outcome)) return; if(data.DB.outcome.result === "error") { console.log("[OutcomeDB] Failed crime attempt."); console.log(JSON.stringify(data)); return; } console.log("[OutcomeDB] Found outcome."); console.log("[OutcomeDB] Preparing bundle."); serverTime = data.DB.time; let bundle = {}; bundle.outcome = data.DB.outcome; bundle.typeID = resource.split("typeID=")[1].split("&")[0]; bundle.crimeID = resource.split("crimeID=")[1].split("&")[0]; bundle.skillBefore = getStat("Skill"); bundle.skillAfter = data.DB.currentUserStatistics[0].value; bundle.progressionBonus = getStat("Progression bonus"); if(!bundle.skillBefore || !bundle.skillAfter || !bundle.progressionBonus) return; bundle.additionalData = getAdditionalData(data.DB.crimesByType, bundle.typeID, resource); console.log("[OutcomeDB] Ready to send bundle.", JSON.stringify(bundle)); sendBundleToAPI(bundle); currentCrimesByTypeData = data.DB.crimesByType; } catch (e) { if(e instanceof SyntaxError) return; handleError("crime_attempt_parse", JSON.stringify({error: {message: e.message, stack: e.stack}, body: body})); } } function handleCrimesList(body, resource) { console.log("[OutcomeDB] Updating crimes data."); //Most likely cloudflare turnstile or server error if(containsHtml(body)) { console.error("[OutcomeDB] Unexpected HTML data, skipping..."); return; } try { let data = JSON.parse(body); if (data.error) { console.log("[OutcomeDB] Failed crimesList: " + data.error); console.log(JSON.stringify(data)); return; } if (!(data.DB && data.DB.crimesByType)) return; currentCrimesByTypeData = data.DB.crimesByType; serverTime = data.DB.time; } catch (e) { if(e instanceof SyntaxError) return; handleError("crime_list_parse", JSON.stringify({error: {message: e.message, stack: e.stack}, body: body})); } } function sendBundleToAPI(bundle) { GM_xmlhttpRequest({ method: "POST", url: "https://api.lzpt.io/outcomedb", headers: {"Content-Type": "application/json"}, data: JSON.stringify(bundle), onload: function (response) { if(!response) return; // because pda doesn't know how to network i guess if(containsHtml(response.responseText)) { console.error("[OutcomeDB] lzpt rate limit hit, skipping..."); return; } console.log("[OutcomeDB] Bundle successfully sent to API:", response.responseText); const json = JSON.parse(response.responseText); if (json.error) { handleError("post_invalid", JSON.stringify({error: {responseText: response.responseText}})); } }, onerror: function (e) { handleError("api_post_error", JSON.stringify({error: {message: e.statusText, status: e.status, full: e}})); } }); } function getStat(name) { let allStatisticButtons = Array.from(win.document.querySelectorAll('li[class^="statistic___"]')); let statButton = allStatisticButtons.find(button => { return Array.from(button.querySelectorAll('span')).some(span => span.textContent.trim() === name); }); if (statButton) { let valueSpan = statButton.querySelector('span[class^="value___"]'); if (valueSpan) { console.log(`[OutcomeDB] Found stat (${name}): '${valueSpan.textContent}'`); return valueSpan.textContent; } } handleError("stat_missing", JSON.stringify({stat: name})); } function getAdditionalData(attemptData, typeID, resource) { try { if(typeID === "12") return extractScammingData(attemptData, resource); return null; } catch(error) { console.error("[OutcomeDB] Additional data failed, skipping:", error); return null; } } function handleError(name, data) { localStorage.setItem("outcomedb_last_error", JSON.stringify({type: name, data: data, timestamp: (Math.floor(Date.now() / 1000))})); console.error("[OutcomeDB] " + name + ":", data); alert("OutcomeDB error " + name + ". Please check console and report this to Hemicopter [2780600]"); } function copyLastError() { let heading = win.document.querySelectorAll('h4[class^="heading___"]')[0]; let content = localStorage.getItem("outcomedb_last_error"); if (!content) return; navigator.clipboard.writeText(content); setTimeout(resetHeading, 1000, heading, heading.innerHTML); heading.innerHTML = "Copied successfully."; } function resetHeading(heading, text) { heading.innerHTML = text; } function registerCopyError() { let heading = win.document.querySelectorAll('h4[class^="heading___"]')[0]; heading.addEventListener("click", copyLastError); } function containsHtml(text) { return text.includes("!DOCTYPE") || text.includes("!doctype") || text.includes("<html") || text.includes("<head") || text.includes("<body"); } function extractScammingData(attemptData, resource) { //However Laz did that if(!currentCrimesByTypeData) return null; //Is it linked to a target? if(!(resource.includes("value1"))) return null; let subID = resource.split("value1=")[1].split("&")[0]; console.log("[OutcomeDB] Extracting additional scamming data."); //Get target states let beforeTargetState = currentCrimesByTypeData.targets.find((target) => {return String(target.subID).includes(subID);}); let afterTargetState = attemptData.targets.find((target) => {return String(target.subID).includes(subID);}); let additionalData = {}; //target information additionalData.gender = beforeTargetState.gender? beforeTargetState.gender : afterTargetState.gender; additionalData.target = beforeTargetState.target? beforeTargetState.target : afterTargetState.target; //action information if(!beforeTargetState.bar) additionalData.action = "read"; else if(!afterTargetState) additionalData.action = "capitalize"; else additionalData.action = afterTargetState.lastAction; const transformBar = (bar) => { if (!bar) return null; const stateMapping = { "neutral": "n", "fail": "f", "low": "l", "medium": "m", "high": "h", "sensitivity": "s", "temptation": "t", "hesitation": "w", "concern": "c" }; return bar.map(state => stateMapping[state] || '?').join(''); }; if (beforeTargetState) { additionalData.targetBefore = { "multiplierUsed": beforeTargetState.multiplierUsed, "pip": beforeTargetState.pip, "turns": beforeTargetState.turns, "bar": transformBar(beforeTargetState.bar) }; } if (afterTargetState) { additionalData.targetAfter = { "multiplierUsed": afterTargetState.multiplierUsed, "pip": afterTargetState.pip, "turns": afterTargetState.turns, "bar": transformBar(afterTargetState.bar) }; additionalData.cooldown = afterTargetState.cooldown ? Math.floor(afterTargetState.cooldown - serverTime) : null; } //targetAfter is missing on capitalize if(!additionalData.targetAfter) additionalData.targetAfter = {}; console.log("[OutcomeDB] Additional data gathered."); return additionalData; } })();