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