Displays bars, cooldowns, and battle stats (with total) for specific users on their Torn profile. PDA friendly.
// ==UserScript==
// @name Torn API User Profile
// @namespace http://tampermonkey.net/
// @version 1.1
// @description Displays bars, cooldowns, and battle stats (with total) for specific users on their Torn profile. PDA friendly.
// @author Primordial
// @match https://www.torn.com/profiles.php*
// @grant GM_addStyle
// @license MIT
// ==/UserScript==
(function() {
'use strict';
// ==========================================
// CONFIGURATION: Add your special users here
// ==========================================
const specialUsers = [
{ user: 1, apiKey: "n/a" },
{ user: 1, apiKey: "n/a" }
];
// ==========================================
// LOGIC & EXECUTION
// ==========================================
const urlParams = new URLSearchParams(window.location.search);
const targetId = parseInt(urlParams.get('XID'));
if (!targetId) return;
const userData = specialUsers.find(u => u.user === targetId);
if (!userData) return;
const observer = new MutationObserver((mutations, obs) => {
const profileWrapper = document.querySelector('.profile-wrapper');
if (profileWrapper && !document.getElementById('custom-stats-panel')) {
obs.disconnect();
fetchAndRenderData(userData.apiKey, profileWrapper);
}
});
observer.observe(document, { childList: true, subtree: true });
// ==========================================
// FUNCTIONS
// ==========================================
async function fetchAndRenderData(apiKey, container) {
try {
const response = await fetch(`https://api.torn.com/user/?selections=bars,cooldowns,battlestats&key=${apiKey}`);
const data = await response.json();
if (data.error) {
console.error("Torn API Error:", data.error);
return;
}
injectUI(data, container);
} catch (error) {
console.error("Failed to fetch special user data:", error);
}
}
function formatTime(seconds) {
if (seconds <= 0) return "00:00:00";
const h = Math.floor(seconds / 3600).toString().padStart(2, '0');
const m = Math.floor((seconds % 3600) / 60).toString().padStart(2, '0');
const s = (seconds % 60).toString().padStart(2, '0');
return `${h}:${m}:${s}`;
}
function injectUI(data, profileWrapper) {
const statsPanel = document.createElement('div');
statsPanel.id = 'custom-stats-panel';
statsPanel.className = 'profile-container mt10';
statsPanel.style.marginBottom = '10px';
// Destructure API data
const { energy, nerve, happy, life } = data;
const cd = data.cooldowns;
const bs = data;
// Calculate Total Battle Stats
const totalStats = (bs.strength || 0) + (bs.speed || 0) + (bs.dexterity || 0) + (bs.defense || 0);
// Responsive grid handles Torn PDA's narrow mobile viewport automatically
statsPanel.innerHTML = `
<div class="title-black top-round" style="display: flex; justify-content: space-between; align-items: center; padding: 0 10px; cursor: pointer;" id="custom-stats-header">
<div class="text">Live Status & Stats</div>
<div class="options"></div>
<svg id="custom-stats-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor" width="1em" height="1em" class="icon" style="transition: transform 0.2s;">
<path d="M213.66,101.66l-80,80a8,8,0,0,1-11.32,0l-80-80A8,8,0,0,1,48,88H208a8,8,0,0,1,5.66,13.66Z"></path>
</svg>
</div>
<div id="custom-stats-content" class="bottom-round cont-gray" style="display: block; padding: 10px; background-color: var(--default-bg-panel-color); color: var(--default-color);">
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); gap: 15px;">
<!-- Bars -->
<div>
<h4 style="border-bottom: 1px solid var(--default-panel-divider-color, #444); margin-bottom: 5px; padding-bottom: 3px;">Bars</h4>
<div><strong>Energy:</strong> ${energy.current} / ${energy.maximum}</div>
<div><strong>Nerve:</strong> ${nerve.current} / ${nerve.maximum}</div>
<div><strong>Happy:</strong> ${happy.current} / ${happy.maximum}</div>
<div><strong>Life:</strong> ${life.current} / ${life.maximum}</div>
</div>
<!-- Cooldowns -->
<div>
<h4 style="border-bottom: 1px solid var(--default-panel-divider-color, #444); margin-bottom: 5px; padding-bottom: 3px;">Cooldowns</h4>
<div><strong>Medical:</strong> ${formatTime(cd.medical)}</div>
<div><strong>Drug:</strong> ${formatTime(cd.drug)}</div>
<div><strong>Booster:</strong> ${formatTime(cd.booster)}</div>
</div>
<!-- Battle Stats -->
<div>
<h4 style="border-bottom: 1px solid var(--default-panel-divider-color, #444); margin-bottom: 5px; padding-bottom: 3px;">Battle Stats</h4>
<div><strong>Strength:</strong> ${bs.strength ? bs.strength.toLocaleString() : 'N/A'}</div>
<div><strong>Speed:</strong> ${bs.speed ? bs.speed.toLocaleString() : 'N/A'}</div>
<div><strong>Dexterity:</strong> ${bs.dexterity ? bs.dexterity.toLocaleString() : 'N/A'}</div>
<div><strong>Defense:</strong> ${bs.defense ? bs.defense.toLocaleString() : 'N/A'}</div>
<div style="margin-top: 5px; padding-top: 5px; border-top: 1px dashed var(--default-panel-divider-color, #444);">
<strong>Total:</strong> ${totalStats > 0 ? totalStats.toLocaleString() : 'N/A'}
</div>
</div>
</div>
</div>
`;
profileWrapper.insertBefore(statsPanel, profileWrapper.firstChild);
// Collapsible Logic
const header = document.getElementById('custom-stats-header');
const content = document.getElementById('custom-stats-content');
const icon = document.getElementById('custom-stats-icon');
header.addEventListener('click', () => {
if (content.style.display === 'none') {
content.style.display = 'block';
icon.style.transform = 'rotate(0deg)';
} else {
content.style.display = 'none';
icon.style.transform = 'rotate(180deg)';
}
});
}
})();