Greasy Fork is available in English.
Track player status, hospital time, and attack links in Torn with persistent data, enhanced UI. Adds players by user ID.
// ==UserScript==
// @name Torn Player Tracker
// @namespace https://www.torn.com/
// @version 2.0
// @description Track player status, hospital time, and attack links in Torn with persistent data, enhanced UI. Adds players by user ID.
// @author Xenocide [2216313]
// @match https://www.torn.com/*
// @grant GM_xmlhttpRequest
// @grant GM_addStyle
// @connect api.torn.com
// ==/UserScript==
(function () {
"use strict";
// === Configuration ===
const UPDATE_INTERVAL = 30000; // Update interval in milliseconds (30 seconds)
// Retrieve API key from localStorage or prompt user to enter it
let API_KEY = localStorage.getItem("tornAPIKey");
// If API key is not found, prompt the user to input it
if (!API_KEY) {
const apiKeyInputSection = document.createElement("div");
apiKeyInputSection.innerHTML = `
<div id="apiKeySection" style="position: fixed; top: 10px; right: 10px; background-color: #333; color: white; padding: 10px; border-radius: 8px; z-index: 10001; font-family: Arial, sans-serif;">
<h3>Enter Your Torn API Key</h3>
<input type="text" id="apiKeyInput" placeholder="API Key" style="width: 100%; padding: 5px; margin-bottom: 5px;" />
<button id="saveAPIKeyButton" style="width: 100%; padding: 5px; background-color: green; color: white;">Save API Key</button>
<small style="color: yellow;">You need an API key to fetch player data.</small>
</div>
`;
document.body.appendChild(apiKeyInputSection);
// Save API key when the user submits it
document.getElementById("saveAPIKeyButton").addEventListener("click", () => {
API_KEY = document.getElementById("apiKeyInput").value.trim();
if (API_KEY) {
localStorage.setItem("tornAPIKey", API_KEY); // Save to localStorage
document.getElementById("apiKeySection").style.display = "none"; // Hide the input form
alert("API Key saved successfully!"); // Provide feedback to the user
} else {
alert("Please enter a valid API Key.");
}
});
}
// If API key is already stored, proceed with the script as usual
if (API_KEY) {
// Retrieve tracked players from localStorage or initialize default IDs
let trackedPlayers = JSON.parse(localStorage.getItem("trackedPlayers")) || [123456, 654321, 789012];
// === Create the Floating Box ===
const box = document.createElement("div");
box.id = "statusTracker";
box.innerHTML = `
<h3>Player Tracker</h3>
<ul id="statusList">Loading...</ul>
<div id="userControls">
<hr>
<input type="text" id="addPlayerInput" placeholder="Add Player ID" />
<button id="addPlayerButton">Add</button>
<ul id="trackedPlayersList"></ul>
</div>
<button id="toggleUI">Hide Add Section</button>
`;
document.body.appendChild(box);
// Add some styles for the box
GM_addStyle(`
#statusTracker {
position: fixed;
top: 10px;
right: 10px;
width: 350px;
background-color: #333;
color: white;
border: 2px solid #555;
border-radius: 8px;
padding: 10px;
z-index: 10000;
font-family: Arial, sans-serif;
}
#statusTracker h3 {
margin: 0 0 10px 0;
font-size: 16px;
text-align: center;
}
#statusTracker ul {
list-style-type: none;
padding: 0;
margin: 0;
}
#statusTracker li {
margin: 5px 0;
}
#statusTracker li span {
font-weight: bold;
}
#statusTracker input {
width: calc(100% - 60px);
margin-right: 5px;
padding: 5px;
}
#statusTracker button {
padding: 5px;
cursor: pointer;
}
#statusTracker hr {
margin: 10px 0;
border: 0.5px solid #555;
}
`);
// === Function to Format Time in Minutes and Seconds ===
function formatTime(seconds) {
const minutes = Math.floor(seconds / 60);
const remainingSeconds = seconds % 60;
return `${minutes}m ${remainingSeconds}s`;
}
// === Function to Fetch Player Info by ID ===
function fetchPlayerStatus(playerId) {
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: "GET",
url: `https://api.torn.com/user/${playerId}?selections=basic,profile&key=${API_KEY}`,
onload: (response) => {
if (response.status === 200) {
const data = JSON.parse(response.responseText);
// Check for API errors
if (data.error) {
console.error(`Error fetching player ${playerId}:`, data.error);
return resolve({
playerId,
name: `Error: ${data.error.error}`,
status: "Unknown",
hospitalTimeLeft: 0,
attackLink: "",
lastActivity: "N/A",
});
}
// Extract data from API response
const name = data.name || `ID ${playerId}`;
const status = data.status?.state || "Unknown";
const hospitalTimeLeft = data.status?.until ? Math.max(0, Math.ceil((data.status.until * 1000 - Date.now()) / 1000)) : 0;
const attackLink = `https://www.torn.com/loader.php?sid=attack&user2ID=${playerId}`;
const lastActivity = data.last_action?.relative || "N/A";
resolve({ playerId, name, status, hospitalTimeLeft, attackLink, lastActivity });
} else {
console.error(`Error fetching status for player ${playerId}:`, response.status);
reject(`Error fetching status for player ${playerId}`);
}
},
onerror: (error) => {
console.error(`Error fetching status for player ${playerId}:`, error);
reject(`Error fetching status for player ${playerId}`);
},
});
});
}
// === Function to Update the Status List ===
async function updateStatusList() {
const statusList = document.getElementById("statusList");
statusList.innerHTML = "Updating...";
try {
const statusPromises = trackedPlayers.map((id) => fetchPlayerStatus(id));
const statuses = await Promise.all(statusPromises);
statusList.innerHTML = "";
statuses.forEach(({ playerId, name, status, hospitalTimeLeft, attackLink, lastActivity }) => {
const color =
status === "Okay"
? "green"
: status === "Hospital"
? "red"
: status === "Traveling"
? "blue"
: "gray";
const hospitalText = status === "Hospital" ? ` (Time left: ${formatTime(hospitalTimeLeft)})` : "";
const listItem = document.createElement("li");
listItem.innerHTML = `
<span style="color: ${color};">${status}</span> -
<a href="${attackLink}" target="_blank" style="color: yellow;">${name}</a>
(ID: ${playerId})${hospitalText} <br>
<small>Last Activity: ${lastActivity}</small>`;
statusList.appendChild(listItem);
});
} catch (error) {
console.error("Error updating statuses:", error);
statusList.innerHTML = "Error updating statuses.";
}
}
// === Function to Update Tracked Players List ===
function updateTrackedPlayersList() {
const trackedPlayersList = document.getElementById("trackedPlayersList");
trackedPlayersList.innerHTML = "";
trackedPlayers.forEach((playerId) => {
const listItem = document.createElement("li");
listItem.innerHTML = `Player ID: ${playerId} <button data-id="${playerId}" class="removePlayerButton">Remove</button>`;
trackedPlayersList.appendChild(listItem);
});
// Add event listeners to remove buttons
document.querySelectorAll(".removePlayerButton").forEach((button) => {
button.addEventListener("click", (event) => {
const playerId = parseInt(event.target.getAttribute("data-id"));
trackedPlayers = trackedPlayers.filter((id) => id !== playerId);
localStorage.setItem("trackedPlayers", JSON.stringify(trackedPlayers)); // Save to localStorage
updateTrackedPlayersList();
updateStatusList();
});
});
}
// === Add Player by ID from Input ===
document.getElementById("addPlayerButton").addEventListener("click", () => {
const input = document.getElementById("addPlayerInput");
const playerId = parseInt(input.value);
if (!isNaN(playerId) && !trackedPlayers.includes(playerId)) {
trackedPlayers.push(playerId);
localStorage.setItem("trackedPlayers", JSON.stringify(trackedPlayers)); // Save to localStorage
input.value = "";
updateTrackedPlayersList();
updateStatusList();
} else if (isNaN(playerId)) {
alert("Please enter a valid player ID.");
} else {
alert("This player is already tracked.");
}
});
// === Hide/Show Add Section ===
const toggleUI = document.getElementById("toggleUI");
const userControls = document.getElementById("userControls");
// Retrieve the saved state of the visibility (default is "shown")
const isHidden = localStorage.getItem("isUserControlsHidden") === "true";
// If hidden, hide the controls
if (isHidden) {
userControls.style.display = "none";
toggleUI.innerText = "Show Add Section";
} else {
toggleUI.innerText = "Hide Add Section";
}
toggleUI.addEventListener("click", () => {
const isCurrentlyHidden = userControls.style.display === "none";
if (isCurrentlyHidden) {
userControls.style.display = "block";
localStorage.setItem("isUserControlsHidden", "false"); // Store the state
toggleUI.innerText = "Hide Add Section";
} else {
userControls.style.display = "none";
localStorage.setItem("isUserControlsHidden", "true"); // Store the state
toggleUI.innerText = "Show Add Section";
}
});
// === Periodic Updates ===
setInterval(updateStatusList, UPDATE_INTERVAL);
updateStatusList(); // Initial update
updateTrackedPlayersList(); // Initial list update
}
})();