您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Logs detailed info about all cracking attempts.
// ==UserScript== // @name Torn Log Cracking // @namespace https://github.com/SOLiNARY // @version 0.1.13 // @description Logs detailed info about all cracking attempts. // @author Ramin Quluzade, Silmaril [2665762] // @license MIT License // @match https://www.torn.com/loader.php?sid=crimes* // @icon https://www.google.com/s2/favicons?sz=64&domain=torn.com // @grant GM_setValue // @grant GM_getValue // @grant GM_deleteValue // @grant GM_listValues // @grant GM_setClipboard // @grant GM_addStyle // ==/UserScript== (function() { 'use strict'; const viewPortWidthPx = window.innerWidth; const isMobileView = viewPortWidthPx <= 784; const isTampermonkeyEnabled = typeof unsafeWindow !== 'undefined'; const levelsImagePxGap = 35; const levelsArray = [[1], generateNumberRange(2, 24), generateNumberRange(25, 49), generateNumberRange(50, 74), generateNumberRange(75, 99)]; const actions = ['Brute force', 'Crack']; let attached = false; let userId = 'N/A'; if (!isTampermonkeyEnabled) { console.error('[TornLogCracking] Please, install Tampermonkey first!'); alert('[TornLogCracking] Please, install Tampermonkey first!'); return; } const styles = ` div.crimeSlider___uha7d > div.previousCrime___gxp9r, div.currentCrime___KNKYQ, div.nextCrime___uKnJr { transition-duration: 0ms !important; } div.silmarilPopOutText { position: absolute; color: #37b24d; font-size: 16px; display: none; user-select: none; pointer-events: none; text-shadow: 0.5px 0.5px 0.5px black, 0 0 1em black, 0 0 0.2em black; }`; GM_addStyle(styles); try { userId = document.cookie.split('; ').filter( x => x.startsWith('uid=') )[0].split('=')[1]; } catch (e) { userId = 'N/A'; } addExportButton(document.querySelector('div.crimes-app div[class*=appHeader___]')); const targetNode = document.querySelector("div.crimes-app"); const observerConfig = { childList: true, subtree: true }; const observer = new MutationObserver(async (mutationsList, observer) => { const crimesDiv = document.querySelectorAll("div.crimes-app"); const currentCrimeDiv = document.querySelectorAll("div[class*=currentCrime___]"); for (const mutation of mutationsList) { let mutationTarget = mutation.target; if (!attached && mutation.type === 'childList' && mutationTarget.className == 'crime-root cracking-root') { if (document.querySelector('div[class*=currentCrime___] div[class*=title___]') == null) { continue; } attached = true; crimesDiv.forEach((div) => { div.addEventListener("click", function (event) { if (event.target.matches("div.topSection___U7sVi div.linksContainer___LiOTN")) { handleSwitchPage(); } }); }); currentCrimeDiv.forEach((div) => { div.addEventListener("click", function (event) { if (event.target.matches("div.topSection___HchKK div.crimeBanner___LiKtj div.crimeSliderArrowButtons___N_y4N button.arrowButton___gYTVW")) { handleSwitchPage(); } }); }); $(mutationTarget).on("click", "button.commitButton___NYsg8:not(.silmaril-log-cracking)", async function(event){ try { console.log('muta click', mutationTarget, event); const now = document.querySelector('span.server-date-time').textContent.split(' '); const date = now[3]; const time = now[1]; const skillLevelBefore = getCurrentSkillLevelValueV2(); const button = findNearestButtonParent(event.target); const action = button.ariaLabel; const row = button.parentNode.parentNode; let passwordLength = ''; let passwordBefore = ''; try { switch (action) { case 'Brute force, 7 nerve': case 'Crack, 5 nerve': { const passwordChars = isMobileView ? row.querySelectorAll('div.tabletPassword___hnzbl > div:not(.charSlotDummy___s11h5)') : row.querySelectorAll('div.desktopPasswordSection___tucU6 > div:not(.charSlotDummy___s11h5)'); passwordLength = passwordChars.length; passwordBefore = getCurrentPassword(passwordChars); break; } default: break; } } catch (e) { console.error('[TornLogCracking] Error while parsing action', action, e); } while (row.nextElementSibling.querySelector('div.outcomeReward___E34U7:not(.silmaril-log-cracking-parsed)') == null) { await sleep(20); } const outcomeRewardDiv = row.nextElementSibling.querySelector('div.outcomeReward___E34U7'); const result = outcomeRewardDiv.querySelector('p.title___IrNe6').textContent; const gained = []; if (outcomeRewardDiv.querySelector('div.rewards___oRCyz span.reward___dCLvW') != null) { const money = outcomeRewardDiv.querySelector('div.rewards___oRCyz span.reward___dCLvW').textContent; gained.push(money); } try { gained.push(outcomeRewardDiv.querySelector('div.rewards___oRCyz div.reward___Tvvmx').textContent); } catch (e) { console.log('[TornLogCracking] No reward string parsed'); } try { gained.push(outcomeRewardDiv.querySelector('div.rewards___oRCyz div.reward___OmEOo').textContent); } catch (e) { console.log('[TornLogCracking] No crit fail injury string parsed'); } try { gained.push(outcomeRewardDiv.querySelector('div.rewards___oRCyz div.reward___snxxf').textContent); } catch (e) { console.log('[TornLogCracking] No crit fail jail string parsed'); } outcomeRewardDiv .querySelectorAll('div.rewards___oRCyz div.reward___P7K87 img') .forEach(item => { try { gained.push(item.alt); } catch (e) { console.error('[TornLogCracking] Error while parsing non-ammo rewards:', item, e); } }); outcomeRewardDiv .querySelectorAll('div.rewards___oRCyz div.reward___oQH1h') .forEach(item => { try { let ammoQuantity = item.querySelector('div.itemCell___aZaUE.countCell___XzhYQ span.count___hBmtm').textContent; let ammoCodeX = (Math.abs(parseInt(item.querySelector('div.ammoImage___tebhe').style.backgroundPositionX))/70).toFixed(0); let ammoCodeY = (Math.abs(parseInt(item.querySelector('div.ammoImage___tebhe').style.backgroundPositionY))/70).toFixed(0); gained.push(`${ammoQuantity} ammo of type ${ammoCodeX}-${ammoCodeY}`); } catch (e) { console.error('[TornLogCracking] Error while parsing ammo rewards:', item, e); } }); outcomeRewardDiv.classList.add('silmaril-log-cracking-parsed'); const skillLevelAfter = getCurrentSkillLevelValueV2(); let passwordAfter = ''; switch (action) { case 'Brute force, 7 nerve': case 'Crack, 5 nerve': { const passwordChars = isMobileView ? row.querySelectorAll('div.tabletPassword___hnzbl > div:not(.charSlotDummy___s11h5)') : row.querySelectorAll('div.desktopPasswordSection___tucU6 > div:not(.charSlotDummy___s11h5)'); passwordAfter = getCurrentPassword(passwordChars); break; } default: break; } const progressionBonus = document.querySelector('div[class*=statsColumn___] > button[class*=statistic___]:nth-child(3) > span[class*=value___]').textContent; const version = GM_info.script.version; // playerId, date, time, action, skillBefore, skillAfter, targetType, targetService, rigStatus, rigPower, rigMips, rigBruteForceStrength, rigOverheatedComponents, rigHottestComponent, result, gained, progressionBonus, version const targetDiv = row.querySelector('div[class*=targetSection___] div[class*=typeAndServiceWrapper___] div[class*=typeAndService___]'); const targetType = targetDiv.querySelector('span[class*=type___]').innerText; const targetService = targetDiv.querySelector('span[class*=service___]').innerText; const rigDiv = document.querySelector('div[class*=rig___]'); const rigStatus = rigDiv.querySelector('div[class*=heading___] span[class*=status___]').innerText; const rigPower = rigDiv.querySelector('div[class*=power___] span[class*=value___]').innerText; const rigMips = rigDiv.querySelector('div[class*=statistic___][class*=mips___] span[class*=value___]').innerText; const rigBruteForceStrength = rigDiv.querySelector('div[class*=statistic___][class*=strength___] span[class*=value___]').innerText; const rigOverheatedComponents = rigDiv.querySelector('div[class*=statistic___][class*=overheated___] span[class*=value___]').innerText; const rigHottestComponent = rigDiv.querySelector('div[class*=statistic___][class*=hottest___] span[class*=value___]').innerText; let rigClockSpeed = ''; try { const computedStyle = getComputedStyle(rigDiv.querySelector('div[class*=readOnlyKnob___]')); rigClockSpeed = computedStyle.getPropertyValue('--position-on-bar'); } catch (e) { rigClockSpeed = 'N/A'; } const crackingAttempt = new CrackingAttempt(userId, date, time, action, skillLevelBefore, skillLevelAfter, targetType, targetService, passwordLength, passwordBefore, passwordAfter, rigStatus, rigPower, rigMips, rigBruteForceStrength, rigOverheatedComponents, rigHottestComponent, rigClockSpeed, result, gained, progressionBonus, version); const dateSplit = date.split('/'); const timeStampFormatted = `${dateSplit[2]}${dateSplit[1]}${dateSplit[0]}${time.replaceAll(':','')}`; const newGuid = generateGuid(); GM_setValue(`silmaril-torn-log-cracking-attempt-${timeStampFormatted}-${newGuid}`, crackingAttempt); const logsTotal = updateLogsCount(); showPopOutText(`Logged +1! (${logsTotal})`, event.clientX, event.clientY); } catch (e) { console.error('[TornLogCracking] Error while constructing log', e); showPopOutText('Not Logged!', event.clientX, event.clientY, false); } }); observer.disconnect(); } } }); setUpObserver(); function getCurrentPassword(passwordChars) { let password = ''; console.log('passwordChars', passwordChars); passwordChars.forEach((item, index) => { try { console.log('char item', item); const passChar = item.querySelector('span.discoveredChar___mmchE'); if (passChar != null) { password += passChar.innerText; } else { password += item.querySelector('div.encryption1___w7tm_') != null ? '#' : '?'; } } catch (e) { password += '!'; } }); console.log('password returned', password); return password; } function handleSwitchPage() { attached = false; setUpObserver(); } function getRigInfo() { } function addExportButton(root) { const outerExportDiv = document.createElement('div'); outerExportDiv.className = 'title___MqBua'; const exportButton = document.createElement('button'); exportButton.className = 'torn-btn commitButton___uX7k_ silmaril-log-cracking silmaril-log-cracking-export'; exportButton.ariaLabel = 'Copy Cracking logs'; const exportButtonLabel = document.createElement('span'); exportButtonLabel.className = 'title___KAQ9m'; exportButtonLabel.textContent = isMobileView ? 'Copy logs' : 'Copy Cracking logs'; const exportButtonDivider = document.createElement('span'); exportButtonDivider.className = 'divider___RGx7a'; const exportButtonLogCount = document.createElement('span'); exportButtonLogCount.className = 'nerveCost___OoGfz'; exportButtonLogCount.textContent = getAllLogsCount(); exportButton.append(exportButtonLabel, exportButtonDivider, exportButtonLogCount); exportButton.addEventListener('click', exportLogs); const outerClearDiv = document.createElement('div'); outerClearDiv.className = 'title___MqBua'; const clearButton = document.createElement('button'); clearButton.className = 'torn-btn commitButton___uX7k_ silmaril-log-cracking silmaril-log-cracking-clear'; clearButton.ariaLabel = 'Clear Cracking logs'; const clearButtonLabel = document.createElement('span'); clearButtonLabel.textContent = isMobileView ? 'Clear logs' : 'Clear Cracking logs'; clearButton.appendChild(clearButtonLabel); clearButton.addEventListener('click', clearLogs); outerExportDiv.append(exportButton); outerClearDiv.append(clearButton); root.append(outerExportDiv, outerClearDiv); const backButton = document.querySelector('div[class*=appHeader___] > a[class*=link___]'); backButton.addEventListener('click', function(){ attached = false; setUpObserver(); }); } function setUpObserver() { observer.observe(targetNode, observerConfig); } function updateLogsCount(reset = false) { const countSpan = document.querySelector('button.silmaril-log-cracking-export > span.nerveCost___OoGfz'); const count = reset ? 0 : parseInt(countSpan.textContent) + 1; countSpan.textContent = count; return count; } function exportLogs() { let logs = getAllLogs(); let logsRaw = JSON.stringify(logs); GM_setClipboard(logsRaw, 'json'); const exportText = document.querySelector('button.silmaril-log-cracking-export > span.title___KAQ9m'); exportText.textContent = isMobileView ? 'Copied!' : 'Copied to clipboard!'; setTimeout(function(){exportText.textContent = isMobileView ? 'Copy logs' : 'Copy Cracking logs';}, 2500); } function clearLogs() { if (!confirm('Are you sure you want to clear the logs?\r\nMake sure to send them to Emforus first.')) { return; } deleteAllLogs(); updateLogsCount(true); const clearText = document.querySelector('button.silmaril-log-cracking-clear > span'); clearText.textContent = isMobileView ? 'Cleared!' : 'Cleared all logs!'; setTimeout(function(){clearText.textContent = isMobileView ? 'Clear logs' : 'Clear Cracking logs';}, 2500); } function getAllLogs() { const prefix = 'silmaril-torn-log-cracking-attempt-'; const filteredRecords = {}; const allKeys = GM_listValues(); allKeys.forEach((key) => { if (key.startsWith(prefix)) { filteredRecords[key] = GM_getValue(key); } }); return filteredRecords; } function getAllLogsCount() { const allLogsCount = Object.keys(GM_listValues()).length; return allLogsCount; } function deleteAllLogs() { const allKeys = GM_listValues(); allKeys.forEach((key) => { GM_deleteValue(key); }); } function getCurrentSkillLevelValueV2() { let skillLevel = 'N/A'; try { skillLevel = document.querySelector('[class*=value___][class*=copyTrigger___]').textContent; } catch (e) { console.error('[TornLogCracking] Error while parsing skill level using v2', e); return skillLevel; } console.log('skillLevel parsed', skillLevel); return skillLevel; } function getCurrentSkillLevelValue() { let currentSkillLevel = -1; try { if (document.querySelector('div.maxLevel___jTooz') == null) { const computedStyle = getComputedStyle(document.querySelector('div.topSection___HchKK div.currentLevel___vCRVm div.number___L1VcJ')); const levelFirstIndex = Math.round(Math.abs(parseInt(computedStyle.getPropertyValue('background-position-x'), 10)) / levelsImagePxGap); const levelSecondIndex = Math.round(Math.abs(parseInt(computedStyle.getPropertyValue('background-position-y'), 10)) / levelsImagePxGap); const skillLevelBefore = levelsArray[levelFirstIndex][levelSecondIndex]; const skillProgressBar = document.querySelector('div.progressFill___ksrq5'); const skillPercentageBefore = parseInt(skillProgressBar.style.width, 10) + (parseInt(skillProgressBar.style.left, 10) ?? -100); currentSkillLevel = skillLevelBefore + skillPercentageBefore / 100; } else { currentSkillLevel = 100; } } catch (e) { console.error('[TornLogCracking] Error while parsing skill level', e); } return parseFloat(currentSkillLevel.toFixed(2)); } function findNearestButtonParent(element) { let currentElement = element; while (currentElement && currentElement.tagName !== 'BUTTON') { currentElement = currentElement.parentNode; } return currentElement; } function getTextValueWithoutChildren(element) { let text = ''; for (let node of element.childNodes) { if (node.nodeType === Node.TEXT_NODE) { text += node.textContent; } } return text.trim(); } function generateGuid() { const crypto = window.crypto || window.msCrypto; if (!crypto) { return Math.floor(Math.random() * (999999 - 100000) + 100000).toString(); } const data = new Uint16Array(8); crypto.getRandomValues(data); data[3] = (data[3] & 0x0fff) | 0x4000; // Version 4 data[4] = (data[4] & 0x3fff) | 0x8000; // Variant 10 const guid = Array.from(data) .map((byte) => byte.toString(16).padStart(2, '0')) .join(''); return ( guid.substr(0, 8) + '-' + guid.substr(8, 4) + '-' + guid.substr(12, 4) + '-' + guid.substr(16, 4) + '-' + guid.substr(20, 12) ); } function generateNumberRange(start, end) { return Array.from({ length: end - start + 1 }, (_, index) => start + index); } function showPopOutText(text, mouseX, mouseY, isSuccess = true, delay = 2000) { // Create pop-out text element let popOutText = document.createElement('div'); popOutText.className = 'silmarilPopOutText'; popOutText.style.color = isSuccess ? '#37b24d' : '#f03e3e'; popOutText.innerText = text; // Append element to the body document.body.appendChild(popOutText); // Adjust for scroll position let scrollX = window.scrollX || window.pageXOffset; let scrollY = window.scrollY || window.pageYOffset; // Set random position within a specific radius let minAngle = 0.4; // Adjust this angle as needed let maxAngle = 0.6; let minRadius = 50; // Adjust this radius as needed let maxRadius = 250; let angle = (Math.random() * (maxAngle - minAngle) + minAngle) * Math.PI * 2; let radius = (Math.random() * (maxRadius - minRadius) + minRadius); let randomX = mouseX + Math.cos(angle) * radius + scrollX; let randomY = mouseY + Math.sin(angle) * radius + scrollY; // Set pop-out text position popOutText.style.left = randomX + 'px'; popOutText.style.top = randomY + 'px'; // Show pop-out text popOutText.style.display = 'block'; // Fade away and disappear after a few seconds setTimeout(function() { popOutText.style.opacity = 0; setTimeout(function() { document.body.removeChild(popOutText); }, 500); // 500ms delay for removal after fade }, delay); // 2000ms (2 seconds) delay for fade } function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } class CrackingAttempt { constructor(playerId, date, time, action, skillBefore, skillAfter, targetType, targetService, passwordLength, passwordBefore, passwordAfter, rigStatus, rigPower, rigMips, rigBruteForceStrength, rigOverheatedComponents, rigHottestComponent, rigClockSpeed, result, gained, progressionBonus, version) { this.playerId = playerId; this.date = date; this.time = time; this.action = action; this.skillBefore = skillBefore; this.skillAfter = skillAfter; this.targetType = targetType; this.targetService = targetService; this.passwordLength = passwordLength; this.passwordBefore = passwordBefore; this.passwordAfter = passwordAfter; this.rigStatus = rigStatus; this.rigPower = rigPower; this.rigMips = rigMips; this.rigBruteForceStrength = rigBruteForceStrength; this.rigOverheatedComponents = rigOverheatedComponents; this.rigHottestComponent = rigHottestComponent; this.rigClockSpeed = rigClockSpeed; this.result = result; this.gained = gained; this.progressionBonus = progressionBonus; this.version = version; } } })();