Tracks CPR data for organized crimes by intercepting Fetch requests, compatible with TornPDA and PC
// ==UserScript==
// @name Torn Faction CPR Tracker
// @namespace http://tampermonkey.net/
// @version 1.1.2
// @description Tracks CPR data for organized crimes by intercepting Fetch requests, compatible with TornPDA and PC
// @author Allenone[2033011], IceBlueFire[776], Oxiblurr [1712955]
// @license MIT
// @match https://www.torn.com/factions.php?step=your*
// @run-at document-idle
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_deleteValue
// @grant GM_xmlhttpRequest
// @connect ufd.abhinavkm.com
// ==/UserScript==
(function() {
'use strict';
const TARGET_URL_BASE = 'page.php?sid=organizedCrimesData&step=crimeList';
const STORAGE_PREFIX = 'UFDFactionCPRTracker_';
const isTornPDA = typeof window.flutter_inappwebview !== 'undefined';
// Storage functions
const getValue = isTornPDA
? (key, def) => JSON.parse(localStorage.getItem(key) || JSON.stringify(def))
: GM_getValue;
const setValue = isTornPDA
? (key, value) => localStorage.setItem(key, JSON.stringify(value))
: GM_setValue;
const deleteValue = isTornPDA
? (key, value) => localStorage.removeItem(key)
: GM_deleteValue;
// HTTP request function
const xmlhttpRequest = isTornPDA
? (details) => {
window.flutter_inappwebview.callHandler('PDA_httpPost', details.url, details.headers, details.data)
.then(response => {
details.onload({
status: response.status,
responseText: response.data
});
})
.catch(err => details.onerror(err));
}
: GM_xmlhttpRequest;
// API key handling
let API_KEY;
if (isTornPDA) {
API_KEY = "#############"; // Hardcoded for TornPDA. Set this to your Torn Stats API key.
setValue(`${STORAGE_PREFIX}api_key`, API_KEY);
} else {
API_KEY = getValue(`${STORAGE_PREFIX}api_key`, null);
if (!API_KEY) {
API_KEY = prompt('Please enter a Public Torn API Key');
if (!API_KEY) {
alert('Faction CPR Tracker: API key is required for functionality.');
return;
}
setValue(`${STORAGE_PREFIX}api_key`, API_KEY);
if (API_KEY) {
submitCPRData(API_KEY, getValue(`${STORAGE_PREFIX}CheckpointPassRates`, {}));
}
}
}
async function submitCPRData(apiKey, checkpointPassRates) {
return new Promise((resolve, reject) => {
xmlhttpRequest({
method: 'POST',
url: `https://ufd.abhinavkm.com/cpr/submit?api_key=${apiKey}`,
headers: { 'Content-Type': 'application/json' },
data: JSON.stringify(checkpointPassRates ),
onload: (response) => {
try {
const jsonResponse = JSON.parse(response.responseText);
console.log('Response JSON:', jsonResponse);
} catch (e) {
console.error('Could not parse JSON:', e);
}
if (response.status >= 200 && response.status < 300) {
console.log('CPR data submitted successfully');
resolve();
} else {
console.error('API error:', response.status, response.responseText);
reject(new Error('API error'));
}
},
onerror: (err) => {
console.error('Submission error:', err);
reject(err);
}
});
});
}
function processCPRs(data) {
const scenarioName = String(data.scenario.name);
let CheckpointPassRates = getValue(`${STORAGE_PREFIX}CheckpointPassRates`, {});
if (!CheckpointPassRates[scenarioName]) {
CheckpointPassRates[scenarioName] = {};
data.playerSlots.forEach(slot => {
const slotName = String(slot.name);
CheckpointPassRates[scenarioName][slotName] = slot.player === null ? slot.successChance : 0;
});
} else {
data.playerSlots.forEach(slot => {
const slotName = String(slot.name);
if (slot.player === null) {
CheckpointPassRates[scenarioName][slotName] = slot.successChance;
}
});
}
setValue(`${STORAGE_PREFIX}CheckpointPassRates`, CheckpointPassRates);
}
// Fetch Interception
const win = isTornPDA ? window : (typeof unsafeWindow !== 'undefined' ? unsafeWindow : window);
const originalFetch = win.fetch;
win.fetch = async function(resource, config) {
const url = typeof resource === 'string' ? resource : resource.url;
if (config?.method?.toUpperCase() !== 'POST' || !url.includes(TARGET_URL_BASE)) {
return originalFetch.apply(this, arguments);
}
let isRecruitingGroup = false;
if (config?.body instanceof FormData) {
isRecruitingGroup = config.body.get('group') === 'Recruiting';
} else if (config?.body) {
isRecruitingGroup = config.body.toString().includes('group=Recruiting');
}
if (!isRecruitingGroup) {
return originalFetch.apply(this, arguments);
}
const response = await originalFetch.apply(this, arguments);
try {
const json = JSON.parse(await response.clone().text());
if (json.success && json.data && json.data.length > 1) {
json.data.forEach(processCPRs);
const API_KEY = getValue(`${STORAGE_PREFIX}api_key`, null);
if (API_KEY) {
submitCPRData(API_KEY, getValue(`${STORAGE_PREFIX}CheckpointPassRates`, {}));
}
}
} catch (err) {
console.error('Error processing response:', err);
}
return response;
};
})();