您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Show CPR data, add status icon to unavailable, highlight member inefficiency
// ==UserScript== // @name OC Track CPR // @namespace heartflower.torn // @version 1.0.6 // @description Show CPR data, add status icon to unavailable, highlight member inefficiency // @author Heartflower // @match https://www.torn.com/* // @icon https://www.google.com/s2/favicons?sz=64&domain=torn.com // @grant GM.xmlHttpRequest // @grant GM_registerMenuCommand // ==/UserScript== (function() { 'use strict'; console.log('[HF] OC Track CPR running'); // API SETTINGS // let apiKey; let storedAPIKey = localStorage.getItem('hf-tornstats-apiKey'); if (storedAPIKey) { apiKey = storedAPIKey; if (typeof GM_registerMenuCommand === 'function') GM_registerMenuCommand('Remove API key', removeAPIKey); } else { setAPIkey(); } let pda = ('xmlhttpRequest' in GM); let httpRequest = pda ? 'xmlhttpRequest' : 'xmlHttpRequest'; let settings = {}; let savedSettings = localStorage.getItem('hf-oc-cpr-settings'); if (savedSettings) { try { settings = JSON.parse(savedSettings); } catch (e) { console.warn('[HF] Failed to parse saved settings, using defaults'); settings = {}; } } let tornStatsData = {}; let cachedTornStatsData = localStorage.getItem('hf-cached-ts-oc-data'); if (cachedTornStatsData) { try { tornStatsData = JSON.parse(cachedTornStatsData); } catch (e) { console.warn('[HF] Failed to parse cached TornStats data'); tornStatsData = {}; } } let localData = {}; let cachedLocalData = localStorage.getItem('hf-cached-local-oc-data'); if (cachedLocalData) { try { localData = JSON.parse(cachedLocalData); } catch (e) { console.warn('[HF] Failed to parse cached local data'); localData = {}; } } let crimeLevelData = {}; let storedLevels = localStorage.getItem('hf-oc-level-data'); if (storedLevels) { try { crimeLevelData = JSON.parse(storedLevels); } catch (e) { console.warn('[HF] Failed to parse crime level data'); crimeLevelData = {}; } } let difficultyTiers = { 1: 'introductory', 2: 'simple', 3: 'intermediate', 4: 'advanced', 5: 'elaborate', } function setAPIkey() { let enterAPIKey = prompt('Enter the API key you used to create TornStats here:'); if (enterAPIKey !== null && enterAPIKey.trim() !== '') { localStorage.setItem('hf-tornstats-apiKey', enterAPIKey); alert('API key set succesfully'); apiKey = enterAPIKey; if (typeof GM_registerMenuCommand === 'function') GM_registerMenuCommand('Remove API key', removeAPIKey); } else { alert('No valid API key entered!'); if (typeof GM_registerMenuCommand === 'function') GM_registerMenuCommand('Set API key', setAPIkey); } } function removeAPIKey() { let wantToDelete = confirm('Are you sure you want to remove your API key?'); if (wantToDelete) { localStorage.removeItem('hf-tornstats-apiKey'); alert('API key successfully removed.'); } else { alert('API key not removed.'); } } // REST OF THE SCRIPT // function hookFetch(target) { if (!target || !target.fetch) return; const originalFetch = target.fetch; target.fetch = function (...args) { return originalFetch.apply(this, args).then(async (response) => { const cloned = response.clone(); let text; try { text = await cloned.text(); } catch (e) { text = '[Could not read response]'; } let url = args[0]; if (!url) return response; // If url is a Request object if (url instanceof Request) url = url.url; if (url.includes('usersNotInvolved')) { try { listenRecruitBtn(JSON.parse(text)); } catch (err) { console.error('[HF] Failed to parse usersNotInvolved:', err); } } else if (url.includes('crimeList')) { try { crimeList(JSON.parse(text)); } catch (err) { console.error('[HF] Failed to parse crimeList:', err); } } return response; // return original so site still works }); }; } async function handleUninvoled(data, uninvolvedEls, lists) { let statuses = await fetchMembers(); for (let user of data.users) { let userId = user.userID; let status = statuses[userId]; for (let element of uninvolvedEls) { let elementUserId = Number(element.href.replace('https://www.torn.com', '').replace('/profiles.php?XID=', '')); if (elementUserId === userId) { let username = element.textContent; let existingIcon = element.parentNode.querySelector('.hf-activity-icon'); if (existingIcon) break; let icon = document.createElement('div'); icon.classList.add('hf-activity-icon'); if (status === 'Online') { icon.style.backgroundPosition = '0 0'; } else if (status === 'Idle') { icon.style.backgroundPosition = '-1098px 0'; } else if (status === 'Offline') { icon.style.backgroundPosition = '-18px 0'; } icon.addEventListener('click', function() { createCPRmodal(username, userId); }); element.parentNode.style.display = 'flex'; element.parentNode.style.alignItems = 'center'; element.parentNode.prepend(icon); element.parentNode.parentNode.style.gridTemplateColumns = 'repeat(auto-fill, minmax(125px, 1fr))'; break; } } } for (let list of lists) { let a = list.querySelector('a'); if (!a) list.style.display = 'none'; } } async function listenRecruitBtn(data) { let recruitBtn = document.body.querySelector('.button___cwmLf'); if (recruitBtn.classList.contains('active___ImR61')) { findCrimeRoot(data); } else { recruitBtn.addEventListener('click', function() { findCrimeRoot(data); }); } } async function crimeList(data) { let members = {}; let existingMembers = localStorage.getItem('hf-cached-local-oc-data'); if (existingMembers) { try { members = JSON.parse(existingMembers); } catch (e) { console.warn('[HF] Failed to parse existing members data'); members = {}; } } for (let crime of data.data) { let crimeName = crime.scenario.name; let roles = []; let cpr = {}; let slots = crime.playerSlots; for (let slot of slots) { let userId = slot.player?.ID; if (!userId) continue; cpr[userId] = slot.successChance; let position = slot.name.replace(/ #\d+$/, ""); if (!roles.includes(position)) roles.push(position); members[userId] = members[userId] || {}; members[userId][crimeName] = members[userId][crimeName] || {}; if (!members[userId][crimeName][position] || Number(members[userId][crimeName][position]) < Number(slot.successChance)) { members[userId][crimeName][position] = slot.successChance; } } if (crimeName && crime.scenario.level && crime.scenario.difficultyTier && !crimeLevelData[crimeName]) { crimeLevelData[crimeName] = { 'level': crime.scenario.level, 'difficulty': difficultyTiers[crime.scenario.difficultyTier], 'roles': roles, } } let crimeId = crime.ID; let crimeEl = await findOC(crimeId); let slotEls = crimeEl?.querySelectorAll('.slotBody___oxizq'); if (!slotEls || slotEls.length < 2) continue; for (let slotEl of slotEls) { let wrapper = slotEl.parentNode.parentNode; if (wrapper.classList.contains('waitingJoin___jq10k')) continue; let crimeName = wrapper.parentNode.querySelector('.panelTitle___aoGuV')?.textContent; let role = wrapper.querySelector('.title___UqFNy')?.textContent.replace(/ #\d+$/, ""); let a = slotEl.querySelector('.slotMenuItem___vkbGP'); if (!a) continue; let userId = Number(a.href.replace('https://www.torn.com', '').replace('/profiles.php?XID=', '')); highlightCPR(cpr, userId, slotEl, crimeName, role); showCPRinfo(a.parentNode, userId); } } localData = members; localStorage.setItem('hf-cached-local-oc-data', JSON.stringify(localData)); localStorage.setItem('hf-oc-level-data', JSON.stringify(crimeLevelData)); } async function highlightCPR(cpr, userId, slotEl, crimeName, role) { let unavailable = false; let slotIcon = slotEl.parentNode.querySelector('.slotIcon___VVnQy'); let svg = slotIcon?.querySelector('svg'); let path = svg?.querySelector('path'); if (path?.getAttribute('fill') === '#ff794c') unavailable = true; let active = document.body.querySelector('.active___ImR61').textContent; if (active === 'Completed') unavailable = false; let slotHeader = slotEl.parentNode.querySelector('.slotHeader___K2BS_'); if (!settings[crimeName]) settings[crimeName] = {}; if (!settings[crimeName][role]) settings[crimeName][role] = 65; if (settings.highlight === 'true' && (Number(cpr[userId]) < Number(settings[crimeName][role]) || unavailable === true)) { slotEl.style.background = 'var(--default-bg-17-gradient)'; // Red slotHeader.style.background = 'var(--default-bg-17-gradient)'; // Red } } async function showCPRinfo(slotMenu, userId) { let existingBtn = slotMenu.querySelector('.hf-cpr-data-btn'); if (existingBtn) return; let span = document.createElement('span'); span.textContent = 'CPR Data'; span.classList.add('slotMenuItem___vkbGP'); span.classList.add('hf-cpr-data-btn'); slotMenu.appendChild(span); let username = slotMenu.parentNode?.querySelector('.badge___E7fuw')?.textContent; if (!username) return; span.addEventListener('click', function() { createCPRmodal(username, userId); }); } function fetchTornStatsData() { let apiUrl = `https://www.tornstats.com/api/v2/${apiKey}/faction/cpr`; GM[httpRequest]({ method: 'GET', url: apiUrl, responseType: 'json', onload: function(response) { try { response.response ??= JSON.parse(response.responseText); // In order for it to work with Torn PDA let data = response.response; tornStatsData = data.members; localStorage.setItem('hf-cached-ts-oc-data', JSON.stringify(tornStatsData)); } catch (error) { console.warn('TornStats server down:', error); return; } }, onerror: function(response) { console.error('Error fetching TornStats data:', response); } }); } // HELPER function to create the SETTINGS modal function createCPRmodal(username, userId, retries = 30) { let tornStats = true; let crimeData = tornStatsData[userId]; if (!crimeData) { tornStats = false; if (localData && localData[userId]) crimeData = localData[userId]; } let mobile = !document.body.querySelector('.searchFormWrapper___LXcWp'); let modal = document.createElement('div'); modal.classList.add('hf-modal'); document.body.appendChild(modal); // Prevent body scrolling const scrollY = window.scrollY; document.body.style.position = 'fixed'; document.body.style.top = `-${scrollY}px`; document.body.style.width = '100%'; let cancelButton = document.createElement('button'); cancelButton.textContent = '✕'; cancelButton.classList.add('hf-cancel-btn'); cancelButton.addEventListener('click', function () { document.body.style.position = ''; document.body.style.top = ''; document.body.style.width = ''; window.scrollTo(0, scrollY); modal.remove(); }); modal.appendChild(cancelButton); let titleContainer = document.createElement('div'); titleContainer.textContent = `${username} CPR Data`; titleContainer.classList.add('hf-title'); modal.appendChild(titleContainer); let subTitle = document.createElement('span'); if (tornStats) { subTitle.textContent = 'Gathered by TornStats'; } else if (crimeData) { subTitle.textContent = 'No TornStats data, using localStorage'; } else { subTitle.textContent = 'No data found'; } subTitle.classList.add('hf-subtitle'); modal.appendChild(subTitle); let scrollContainer = document.createElement('div'); scrollContainer.classList.add('hf-scroll-container'); modal.appendChild(scrollContainer); let mainContainer = document.createElement('div'); mainContainer.classList.add('hf-main-container'); scrollContainer.appendChild(mainContainer); if (!crimeData) return; // Convert crimeData into an array so we can sort let crimes = Object.keys(crimeData).map(crime => { if (!crimeLevelData[crime]) { crimeLevelData[crime] = { level: 0, difficulty: 'unknown' }; } let level = crimeLevelData[crime].level; let difficulty = crimeLevelData[crime].difficulty; let difficultyTier = Number(Object.keys(difficultyTiers).find( key => difficultyTiers[key] === difficulty )) || 0; // unknown = 0 return { crime, level, difficulty, difficultyTier }; }); // Sort: difficultyTier high → low, then level high → low crimes.sort((a, b) => { if (a.difficultyTier !== b.difficultyTier) { return b.difficultyTier - a.difficultyTier; } return b.level - a.level; }); // Now loop in sorted order for (let { crime, level, difficulty } of crimes) { if (settings[crime] && settings[crime].hidden && settings[crime].hidden === 'true') continue; let crimeContainer = document.createElement('div'); crimeContainer.classList.add('hf-crime-container'); crimeContainer.classList.add(`hf-${difficulty}`); mainContainer.appendChild(crimeContainer); let crimeTitle = document.createElement('span'); crimeTitle.textContent = `${crime} (${level})`; crimeTitle.classList.add('hf-crime-title'); crimeTitle.classList.add(`hf-${difficulty}`); crimeContainer.appendChild(crimeTitle); if (difficulty === 'unknown') crimeTitle.title = `Find this crime in any planned/finished crimes to complete the data`; let scoreContainer = document.createElement('div'); scoreContainer.classList.add('hf-crime-score-container'); crimeContainer.appendChild(scoreContainer); let roles = Object.entries(crimeData[crime]); // [[role1, 10], [role2, 5], [role3, 15]] // Sort by score descending roles.sort((a, b) => b[1] - a[1]); // Iterate over sorted roles for (let [role, score] of roles) { let score = Number(crimeData[crime][role]); let roleContainer = document.createElement('div'); roleContainer.classList.add('hf-role-container'); if (!settings[crime]) settings[crime] = {}; if (!settings[crime][role]) settings[crime][role] = 65; if (score >= 75) { roleContainer.classList.add('hf-good-cpr'); } else if (score >= 50) { roleContainer.classList.add('hf-medium-cpr'); } else { roleContainer.classList.add('hf-bad-cpr'); } let roleSpan = document.createElement('span'); roleSpan.textContent = role; roleSpan.classList.add('hf-crime-role-span'); roleContainer.appendChild(roleSpan); let scoreSpan = document.createElement('span'); scoreSpan.textContent = score; scoreSpan.classList.add('hf-crime-score-span'); roleContainer.appendChild(scoreSpan); scoreContainer.appendChild(roleContainer); } } return modal; } async function findOC(neededId, retries = 30) { let crimes = document.body.querySelectorAll('.wrapper___U2Ap7'); if (!crimes || crimes.length < 3) { if (retries > 0) { return new Promise(resolve => setTimeout(() => resolve(findOC(neededId, retries - 1)), 100) ); } else { console.warn('[HF] Gave up looking for OCs after 30 retries.'); return null; } } for (let crime of crimes) { let crimeId = crime.getAttribute('data-oc-id'); if (neededId == crimeId) return crime; } } async function findCrimeRoot(data, retries = 30) { let crimeRoot = document.body.querySelector('#faction-crimes-root'); if (!crimeRoot) { if (retries > 0) { setTimeout(() => findCrimeRoot(data, retries - 1), 100); } else { console.warn('[HF] Gave up looking for crime root after 30 retries.'); } return; } createObserver(crimeRoot, data); } async function findUninvolved(node, info, retries = 30) { let uninvolved = node.querySelectorAll('.list___dkw9S .item___kkKxv a'); if (!uninvolved || uninvolved.length < 1) { if (retries > 0) { setTimeout(() => findUninvolved(node, info, retries - 1), 100); } else { console.warn('[HF] Gave up looking for uninvolveds after 30 retries.'); return; } } let lists = node.querySelectorAll('.list___dkw9S .item___kkKxv'); handleUninvoled(info, uninvolved, lists); } async function fetchMembers() { let currentEpoch = Math.floor(Date.now() / 1000); let fromTimestamp = currentEpoch - (7 * 24 * 60 * 60); // One week ago let apiUrl = `https://api.torn.com/v2/faction/members?striptags=true&key=${apiKey}`; try { let response = await fetch(apiUrl); let data = await response.json(); let statuses = {}; for (let member of data.members) { statuses[member.id] = member.last_action.status; } return statuses; } catch (error) { console.error('Error fetching data: ' + error); return {}; // return empty object on error } } function addSettingsTab(retries = 30) { let btnContainer = document.body.querySelector('.buttonsContainer___aClaa'); let contentArea = document.getElementById('oc-content-area'); if (!contentArea) contentArea = document.body.querySelector('.wrapper___U2Ap7')?.parentNode; if (!btnContainer || !contentArea) { if (retries > 0) { setTimeout(() => addSettingsTab(retries - 1), 100); } else { console.warn('[HF] Gave up looking for the button container after 30 retries.'); } return; } let mobile = !document.body.querySelector('.searchFormWrapper___LXcWp'); let otherButtons = btnContainer.querySelectorAll('.button___cwmLf'); let wrappers = contentArea.querySelectorAll(':scope > div'); let div = document.createElement('div'); div.classList.add('hf-cpr-config-container'); contentArea.appendChild(div); showSettings(div); let button = document.createElement('button'); button.textContent = 'CPR Configuration'; if (mobile) button.textContent = 'CPR'; button.classList.add('button___cwmLf'); button.classList.add('hf-cpr-config-btn'); btnContainer.appendChild(button); btnContainer.addEventListener('click', function(e) { const clicked = e.target.closest('.button___cwmLf'); if (!clicked) return; if (clicked !== button) { button.classList.remove('active___ImR61'); div.classList.remove('hf-active'); } }); button.addEventListener('click', function() { for (let button of otherButtons) { if (button.classList.contains('active___ImR61')) button.classList.remove('active___ImR61'); } for (let wrapper of wrappers) { if (wrapper.classList.contains('hf-cpr-config-container')) continue; wrapper.style.display = 'none'; } button.classList.add('active___ImR61'); div.classList.add('hf-active'); }); } function showSettings(element) { let mobile = !document.body.querySelector('.searchFormWrapper___LXcWp'); let titleContainer = document.createElement('div'); titleContainer.classList.add('hf-title-container'); element.appendChild(titleContainer); let title = document.createElement('div'); title.textContent = `CPR Requirements Configuration`; if (mobile) title.textContent = `CPR Requirements Config`; title.classList.add('hf-title'); titleContainer.appendChild(title); let subTitle = document.createElement('span'); subTitle.textContent = 'Configure minimum CPR requirements for each crime and role'; subTitle.classList.add('hf-subtitle'); titleContainer.appendChild(subTitle); let mainContainer = document.createElement('div'); mainContainer.classList.add('hf-main-container'); element.appendChild(mainContainer); let toggleContainer = document.createElement('div'); toggleContainer.classList.add('hf-toggle-container'); mainContainer.appendChild(toggleContainer); let toggle = addToggle(toggleContainer); toggle.addEventListener('change', function () { if (toggle.checked) { settings.highlight = 'true'; } else { settings.highlight = 'false'; } localStorage.setItem('hf-oc-cpr-settings', JSON.stringify(settings)); }); let crimeArray = Object.entries(crimeLevelData); let tierValues = Object.fromEntries( Object.entries(difficultyTiers).map(([key, value]) => [value, Number(key)]) ); crimeArray.sort((a, b) => { let crimeA = a[1]; let crimeB = b[1]; // Sort by difficulty tier (high → low) let diffA = tierValues[crimeA.difficulty] || 0; let diffB = tierValues[crimeB.difficulty] || 0; if (diffB !== diffA) return diffB - diffA; // Sort by level (high → low) return Number(crimeB.level) - Number(crimeA.level); }); // Convert back to object if needed let sortedCrimeData = Object.fromEntries(crimeArray); for (let crimeName in sortedCrimeData) { if (!settings[crimeName]) settings[crimeName] = {}; let crime = sortedCrimeData[crimeName]; if (!crime.roles || crime.roles.length < 2) continue; let crimeContainer = document.createElement('div'); crimeContainer.classList.add('hf-crime-container'); crimeContainer.classList.add(`hf-${crime.difficulty}`); mainContainer.appendChild(crimeContainer); let crimeTitleContainer = document.createElement('div'); crimeTitleContainer.classList.add('hf-crime-title-container'); crimeContainer.appendChild(crimeTitleContainer); let crimeTitle = document.createElement('span'); crimeTitle.textContent = `${crimeName} (${crime.level})`; crimeTitle.classList.add('hf-crime-title'); crimeTitle.classList.add(`hf-${crime.difficulty}`); crimeTitleContainer.appendChild(crimeTitle); let scoreContainer = document.createElement('div'); scoreContainer.classList.add('hf-crime-score-container'); crimeContainer.appendChild(scoreContainer); let hideShowBtn = document.createElement('div'); let hidden = false; if (!settings[crimeName].hidden) settings[crimeName].hidden = false; if (settings[crimeName].hidden === 'true') hidden = true; hideShowBtn.classList.add('hf-oc-hide-show-btn'); function updateState() { scoreContainer.classList.toggle('hf-hidden', hidden); settings[crimeName].hidden = hidden ? 'true' : 'false'; localStorage.setItem('hf-oc-cpr-settings', JSON.stringify(settings)); hideShowBtn.innerHTML = hidden ? `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-eye-off"> <path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8 a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4 c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19 m-6.72-1.07a3 3 0 1 1-4.24-4.24"></path> <line x1="1" y1="1" x2="23" y2="23"></line></svg>` : `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-eye"> <path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path><circle cx="12" cy="12" r="3"></circle></svg>`; } updateState(); hideShowBtn.addEventListener('click', function () { hidden = !hidden; updateState(); }); crimeTitleContainer.appendChild(hideShowBtn); for (let role of crime.roles) { let roleContainer = document.createElement('div'); roleContainer.classList.add('hf-role-container'); scoreContainer.appendChild(roleContainer); let roleSpan = document.createElement('span'); roleSpan.textContent = role; roleSpan.classList.add('hf-crime-role-span'); roleContainer.appendChild(roleSpan); let scoreInput = createNumberInput(crimeName, role, crimeContainer); scoreInput.classList.add('hf-crime-score-input'); roleContainer.appendChild(scoreInput); } } } function createNumberInput(crimeName, role, element) { let className = crimeName.toLowerCase().replace(/\s+/g, '-'); if (!settings[crimeName][role]) { settings[crimeName][role] = 65; localStorage.setItem('hf-oc-cpr-settings', JSON.stringify(settings)); } let input = document.createElement('input'); input.classList.add('hf-number-input'); input.classList.add(className); input.type = 'number'; input.min = 1; input.max = 100; input.value = settings[crimeName][role] || 65; element.appendChild(input); input.addEventListener('input', function () { settings[crimeName][role] = input.value; localStorage.setItem('hf-oc-cpr-settings', JSON.stringify(settings)); }); return input; } function addToggle(element) { let label = document.createElement('label'); label.classList.add('hf-switch'); let text = document.createElement('span'); text.classList.add('hf-input-text'); text.textContent = 'Highlight unavailable and unfit members'; let input = document.createElement('input'); input.type = 'checkbox'; input.classList.add('hf-checkbox'); let slider = document.createElement('span'); slider.classList.add('hf-slider', 'round'); if (settings.highlight === 'true') input.checked = true; label.appendChild(input); label.appendChild(slider); element.appendChild(label); element.appendChild(text); return input; } // HELPER function to create a mutation observer and check nerve function createObserver(element, info) { let target; target = element; if (!target) { console.error(`[HF] Mutation Observer target not found.`); return; } let observer = new MutationObserver(function(mutationsList, observer) { for (let mutation of mutationsList) { if (mutation.type === 'childList') { mutation.addedNodes.forEach(node => { if (node.classList && node.classList.contains('notInvolvedMembers___ifZnn')) { findUninvolved(node, info); } }); } } }); let config = { attributes: true, childList: true, subtree: true, characterData: true }; observer.observe(target, config); } function addStyle(css) { let style = document.createElement("style"); style.textContent = css; document.head.appendChild(style); } function runScript() { if (document.hfOCTrackCpr) return; if (window.location.href.includes('factions') && window.location.href.includes('tab=crimes')) { document.hfOCTrackCpr = true; hookFetch(window); if (typeof unsafeWindow !== 'undefined') hookFetch(unsafeWindow); fetchTornStatsData(); addSettingsTab(); } else { document.hfOCTrackCpr = false; } } runScript(); let lastUrl = location.href; new MutationObserver(() => { if (location.href !== lastUrl) { lastUrl = location.href; runScript(); } }).observe(document, {subtree: true, childList: true}); // Styles addStyle(` .hf-modal { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); padding: 20px 20px 0px 20px; background-color: var(--sidebar-area-bg-attention); border: 2px solid var(--default-tabs-color); border-radius: 15px; max-width: fit-content; width: 60vw; z-index: 9999; max-height: 75vh; display: flex; flex-direction: column; line-height: normal; } .hf-cancel-btn { position: absolute; right: 10px; top: -10px; cursor: pointer; background-color: #CCC; color: black; border-radius: 99px; z-index: 9; font-size: medium; } .hf-title-container { display: flex; flex-direction: column; align-items: center; margin: 0 auto; } .hf-title { font-size: x-large; font-weight: bolder; text-align: center; text-wrap: balance; } .hf-subtitle { text-align: center; padding-bottom: 8px; padding-top: 4px; } .hf-scroll-container { max-height: 100%; flex: 1; overflow-y: auto; margin-top: 8px; padding-bottom: 20px; } .hf-main-container { margin: 0 auto; display: flex; flex-direction: column; } .hf-crime-title-container { display: flex; justify-content: space-between; align-items: center; } .hf-crime-container { background: #516574; border-radius: 5px; margin: 5px; border-left: 4px solid grey; padding: 8px; } .hf-crime-title { font-size: 15px; font-weight: bold; } .hf-crime-score-container { padding-top: 8px; margin-bottom: -4px; } .hf-crime-score-container.hf-hidden { display: none; } .hf-role-container { padding: 4px; display: flex; justify-content: space-between; align-items: center; border: 2px solid #989898; border-radius: 5px; margin-bottom: 6px; background: #647b8c } .hf-crime-score-span { font-weight: bold; } .hf-crime-role-span { color: white; } .hf-crime-score-input { padding: 5px; border-radius: 5px; border: 1px solid #ccc; width: 35px; height: 5px; margin-left: 5px; background: #516574; color: #fff; } .hf-cpr-icon { cursor: pointer; margin-right: 10px; } .hf-oc-hide-show-btn { cursor: pointer; display: flex; } .hf-oc-hide-show-btn svg { width: 20px; height: 20px; } .hf-bad-cpr { background: #ff794c40; } .hf-bad-cpr .hf-crime-score-span { color: #ff794c !important; } .hf-medium-cpr { background: #fcc41940; } .hf-medium-cpr .hf-crime-score-span { color: #fcc419 !important; } .hf-good-cpr { background: #94d82d40; } .hf-good-cpr .hf-crime-score-span { color: #94d82d !important; } .hf-crime-title.hf-introductory { color: #8ce99a; } .hf-crime-container.hf-introductory { border-color: #8ce99a !important; } .hf-crime-title.hf-simple { color: #ffe066; } .hf-crime-container.hf-simple { border-color: #ffe066 !important; } .hf-crime-title.hf-intermediate { color: #ffa94d; } .hf-crime-container.hf-intermediate { border-color: #ffa94d !important; } .hf-crime-title.hf-advanced { color: #ff8787; } .hf-crime-container.hf-advanced { border-color: #ff8787 !important; } .hf-crime-title.hf-elaborate { color: #b197fc; } .hf-crime-container.hf-elaborate { border-color: #b197fc !important; } .hf-crime-title.hf-unknown { color: #9e9e9e; } .hf-crime-container.hf-unknown { border-color: #9e9e9e !important; } .hf-activity-icon { background-image: url(https://www.torn.com/images/v2/svg_icons/sprites/user_status_icons_sprite.svg); height: 17px; width: 17px; margin-right: 4px; cursor: pointer; } .body-no-scroll { overflow: hidden; position: fixed; width: 100%; height: 100%; } .hf-cpr-config-btn { font-size: 12px; } .hf-cpr-config-container { width: max-content; max-width: 90vw; justify-self: center; display: none; margin: 0 auto; } .hf-cpr-config-container.hf-active { display: block !important; } .hf-toggle-container { align-self: center; padding: 8px; } .hf-input-text { padding-left: 5px; } .hf-switch { position: relative; display: inline-block; width: 20px; height: 10px; top: 1px; } .hf-switch input { opacity: 0; width: 0; height: 0; } .hf-slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background: #ccc; transition: .4s; } .hf-slider:before { position: absolute; content: ""; height: 10px; width: 10px; background-color: white; transition: .4s; } input:checked + .hf-slider { background-color: #2196F3; } input:focus + .hf-slider { box-shadow: 0 0 1px #2196F3; } input:checked + .hf-slider:before { transform: translateX(10px); } .hf-slider.round { border-radius: 34px; } .hf-slider.round:before { border-radius: 50%; } `); let colorScheme = { "bad-cpr": "#ff794c", "medium-cpr": "#fcc419", "good-cpr": "#94d82d", "introductory": "#8ce99a", "simple": "#ffe066", "intermediate": "#ffa94d", "advanced": "#ff8787", "elaborate": "#b197fc", }; })();