Torn Player Tracker

Track player status, hospital time, and attack links in Torn with persistent data, enhanced UI. Adds players by user ID.

Você precisará instalar uma extensão como Tampermonkey, Greasemonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Userscripts para instalar este script.

Você precisará instalar uma extensão como o Tampermonkey para instalar este script.

Você precisará instalar um gerenciador de scripts de usuário para instalar este script.

(Eu já tenho um gerenciador de scripts de usuário, me deixe instalá-lo!)

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

(Eu já possuo um gerenciador de estilos de usuário, me deixar fazer a instalação!)

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