// ==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
}
})();