您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Calculates and displays your current crime chain
// ==UserScript== // @name TORN: Display Crime Chain // @namespace http://torn.city.com.dot.com.com // @version 1.0.4 // @description Calculates and displays your current crime chain // @author Ironhydedragon[2428902] // @match https://www.torn.com/loader.php?sid=crimes* // @license MIT // @run-at document-end // ==/UserScript== let crimeChain = 0; const redFlame = '#e64d1a'; const PDA_API_KEY = '###PDA-APIKEY###'; function isPDA() { const PDATestRegex = !/^(###).+(###)$/.test(PDA_API_KEY); console.log('REGEX', PDATestRegex); // TEST return PDATestRegex; } function setApiKey(apiKey) { localStorage.setItem('ihdScriptApiKey', apiKey); } function getApiKey() { return localStorage.getItem('ihdScriptApiKey'); } const stylesheet = ` <style> #crime-chain { cursor: unset; } #api-form.header-wrapper-top { display: flex; } #api-form.header-wrapper-top .container { display: flex; justify-content: start; align-items: center; padding-left: 20px; } #api-form.header-wrapper-top h2 { display: block; text-align: center; margin: 0; width: 172px; } #api-form.header-wrapper-top input { background: linear-gradient(0deg, #111, #000); border-radius: 5px; box-shadow: 0 1px 0 hsla(0, 0%, 100%, 0.102); box-sizing: border-box; color: #9f9f9f; display: inline; font-weight: 400; height: 24px; width: clamp(170px, 50%, 250px); margin: 0 0 0 21px; outline: none; padding: 0 10px 0 10px; font-size: 12px; font-style: italic; vertical-align: middle; border: 0; text-shadow: none; z-index: 100; } #api-form.header-wrapper-top a { margin: 0 8px; } @media screen and (max-width: 1000px) { #api-form.header-wrapper-top h2 { width: 148px; } #api-form.header-wrapper-top input { margin-left: 10px; } } @media screen and (max-width: 784px) { #api-form.header-wrapper-top h2 { font-size: 16px; width: 80px; } #crime-chain .linkTitle____NPyM { display: block; } #body.r .linksContainer___LiOTN { margin-left: 8px; } } </style>`; function renderStylesheet() { document.head.insertAdjacentHTML('beforeend', stylesheet); } function renderApiForm() { const topHeaderBannerEl = document.querySelector('#topHeaderBanner'); const apiFormHTML = ` <div id="api-form" class="header-wrapper-top"> <div class="container clear-fix"> <h2>API Key</h2> <input id="api-form__input" type="text" placeholder="Enter a full-acces API key..." /> <a href="#" id="api-form__submit" type="btn" disabled><span class="link-text">Submit</span</button> </div> </div>`; if (document.querySelector('#api-form')) return; topHeaderBannerEl.insertAdjacentHTML('afterbegin', apiFormHTML); } function dismountApiForm() { document.querySelector('#api-form').remove(); } function renderCrimeChainHTML() { console.log('🖼️ RENDERING CHAIN HTML'); // TEST const crimeChainHTML = ` <div class="linksContainer___LiOTN"> <span aria-labelledby="crime-chain" class="linkContainer___X16y4 inRow___VfDnd greyLineV___up8VP link-container-CrimesHub" target="_self" id="crime-chain" ><span class="iconContainer___D5z6F linkIconContainer___Ep0LO" ><svg fill="#777777" height="17px" width="16px" version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 31.891 31.891" xml:space="preserve"> <g id="SVGRepo_bgCarrier" stroke-width="0"></g> <g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g> <g id="SVGRepo_iconCarrier"> <g> <path d="M30.543,5.74l-4.078-4.035c-1.805-1.777-4.736-1.789-6.545-0.02l-4.525,4.414c-1.812,1.768-1.82,4.648-0.02,6.424 l2.586-2.484c-0.262-0.791,0.061-1.697,0.701-2.324l2.879-2.807c0.912-0.885,2.375-0.881,3.275,0.01l2.449,2.42 c0.9,0.891,0.896,2.326-0.01,3.213l-2.879,2.809c-0.609,0.594-1.609,0.92-2.385,0.711l-2.533,2.486 c1.803,1.781,4.732,1.789,6.545,0.02l4.52-4.41C32.34,10.396,32.346,7.519,30.543,5.74z" ></path> <path d="M13.975,21.894c0.215,0.773-0.129,1.773-0.752,2.381l-2.689,2.627c-0.922,0.9-2.414,0.895-3.332-0.012l-2.498-2.461 c-0.916-0.906-0.91-2.379,0.012-3.275l2.691-2.627c0.656-0.637,1.598-0.961,2.42-0.689l2.594-2.57 c-1.836-1.811-4.824-1.82-6.668-0.02l-4.363,4.26c-1.846,1.803-1.855,4.734-0.02,6.549l4.154,4.107 c1.834,1.809,4.82,1.818,6.668,0.018l4.363-4.26c1.844-1.805,1.852-4.734,0.02-6.547L13.975,21.894z" ></path> <path d="M11.139,20.722c0.611,0.617,1.611,0.623,2.234,0.008l7.455-7.416c0.621-0.617,0.625-1.615,0.008-2.234 c-0.613-0.615-1.611-0.619-2.23-0.006l-7.457,7.414C10.529,19.103,10.525,20.101,11.139,20.722z"></path> </g> </g></svg></span ><span class="linkTitle____NPyM"><span aria-label="current crime chain" id="crime-chain__current">###</span></span></span > </div> `; const titleContainerEl = document.querySelector('.crimes-app .heading___dOsMq'); // const titleContainerEl = document.querySelector('.crimes-app .titleContainer___QrlWP'); if (document.querySelector('#crime-chain')) return; titleContainerEl.insertAdjacentHTML('afterend', crimeChainHTML); } function renderCrimeChainCurrent() { console.log('⛓️', crimeChain); // TEST document.querySelector('#crime-chain__current').textContent = Math.floor(crimeChain); } async function fetchCrimes(toTimestamp) { const response = await fetch(`https://api.torn.com/user/?selections=log&cat=136${toTimestamp ? '&to=' + toTimestamp : ''}&key=${getApiKey()}`); const data = await response.json(); return data; } async function calcCrimeChain() { try { let dataCollector = []; const initialData = await fetchCrimes(); function dataCollectorUnshifter(fetchData) { for (const log in fetchData.log) { if (fetchData.log[log].title.match(/Crime (success|fail|critical fail)/gi)) { dataCollector.unshift(fetchData.log[log]); } } } dataCollectorUnshifter(initialData); while (dataCollector.filter((log) => log.title.match(/Crime critical fail/i)).length < 1) { const data = await fetchCrimes(dataCollector[0].timestamp - 1); dataCollectorUnshifter(data); } for (const d of dataCollector) { if (d.title.match(/Crime success/i)) { crimeChain++; } if (d.title.match(/Crime fail/i)) { crimeChain = crimeChain ? crimeChain / 2 : 0; } if (d.title.match(/Crime critical fail/i)) { crimeChain = 0; } } } catch (error) { console.error(error); // TEST } } //// Callbacks function submitFormCallback() { const inputEl = document.querySelector('#api-form__input'); const submitBtnEl = document.querySelector('#api-form__submit'); const apiKey = inputEl.value; if (apiKey.length !== 16) { inputEl.style.border = `2px solid ${redFlame}`; submitBtnEl.disabled = true; return; } setApiKey(apiKey); dismountApiForm(); window.location.reload(); } function inputValidatorCallback(event) { const inputEl = document.querySelector('#api-form__input'); const submitBtnEl = document.querySelector('#api-form__submit'); if (event.target.value.length === 16) { submitBtnEl.disabled = false; inputEl.style.border = '1px solid #444'; } if (event.target.value.length !== 16) { submitBtnEl.disabled = true; } } function updateCrimeCallback(mutationList) { for (const mutation of mutationList) { if (mutation.addedNodes.length > 0 && mutation.addedNodes[0].classList && [...mutation.addedNodes[0].classList].join(' ').match(/crimes-outcome-/)) { const outcome = [...mutation.addedNodes[0].classList].join(' ').match(/(?<=crimes-outcome-)\w+/gi)[0]; console.log('👀', outcome); // TEST if (outcome === 'success') { crimeChain++; } if (outcome === 'failure') { crimeChain = crimeChain / 2; } if (outcome === 'criticalFailure') { crimeChain = crimeChain / 2; } renderCrimeChainCurrent(); } } } //////// CONTROLLERS //////// function apiKeyFormController() { renderApiForm(); // set event liseners //// Event listeners document.querySelector('#api-form__submit').addEventListener('click', submitFormCallback); document.querySelector('#api-form__input').addEventListener('input', inputValidatorCallback); document.querySelector('#api-form__input').addEventListener('keyup', (event) => { if (event.key === 'Enter' || event.keyCode === 13) { submitFormCallback(); } }); return; } function initController() { renderStylesheet(); if (isPDA()) { console.log('🌟 IS PDA!!!!!', PDA_API_KEY); // TEST setApiKey(PDA_API_KEY); } if (!getApiKey()) { console.log('noAPIKey found'); // TEST apiKeyFormController(); return; } renderCrimeChainHTML(); } async function loadController() { await calcCrimeChain(); renderCrimeChainCurrent(); } function updateCrimeChainController() { const updateCrimeObserver = new MutationObserver(updateCrimeCallback); updateCrimeObserver.observe(document, { attributes: false, childList: true, subtree: true }); } //// Promise race conditions // necessary as PDA scripts are inject after window.onload const PDAPromise = new Promise((res, rej) => { if (document.readyState === 'complete') res(); }); const browserPromise = new Promise((res, rej) => { window.addEventListener('load', () => res()); }); (async () => { try { console.log('⛓️ Crime chain script ON!'); // TEST await Promise.race([PDAPromise, browserPromise]); initController(); if (getApiKey()) { await loadController(); updateCrimeChainController(); } } catch (error) { console.error(error); // TEST } })();