Torn War Tool

Torn War Tool UI

// ==UserScript==
// @name         Torn War Tool 
// @namespace    http://tampermonkey.net/
// @version      1.8
// @description  Torn War Tool UI
// @author       aquagloop
// @match        https://www.torn.com/*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_xmlhttpRequest
// @connect      8627-2a00-23c6-f883-9201-84dd-517c-6864-a404.ngrok-free.app
// @connect      api.torn.com
// @connect      127.0.0.1
// ==/UserScript==

(function () {
  'use strict';

  const backendURL = "https://8627-2a00-23c6-f883-9201-84dd-517c-6864-a404.ngrok-free.app";
  async function getApiKey() {
    let key = await GM_getValue("apiKey");
    if (!key) {
      key = prompt("Enter your Torn API Key:");
      if (key) {
        await GM_setValue("apiKey", key);
      }
    }
    console.log('API key retrieved:', key);
    return key;
  }
  function getRequest(url) {
    return new Promise((resolve, reject) => {
      GM_xmlhttpRequest({
        method: "GET",
        url,
        onload: function (response) {
          try {
            const data = JSON.parse(response.responseText);
            resolve(data);
          } catch (e) {
            reject(e);
          }
        },
        onerror: function (err) {
          reject(err);
        }
      });
    });
  }

  function postRequest(url, data) {
    return new Promise((resolve, reject) => {
      GM_xmlhttpRequest({
        method: "POST",
        url,
        headers: { "Content-Type": "application/json" },
        data: JSON.stringify(data),
        onload: function (response) {
          if (response.status >= 200 && response.status < 300) {
            try {
              resolve(JSON.parse(response.responseText));
            } catch {
              resolve(response.responseText);
            }
          } else {
            reject(new Error(`HTTP error ${response.status}`));
          }
        },
        onerror: function (err) {
          reject(err);
        }
      });
    });
  }

  function deleteRequest(url, data) {
    return new Promise((resolve, reject) => {
      GM_xmlhttpRequest({
        method: "DELETE",
        url,
        headers: { "Content-Type": "application/json" },
        data: JSON.stringify(data),
        onload: function (response) {
          if (response.status >= 200 && response.status < 300) {
            resolve(response.responseText);
          } else {
            reject(new Error(`HTTP error ${response.status}`));
          }
        },
        onerror: function (err) {
          reject(err);
        }
      });
    });
  }

  async function registerApiKey(key) {
    console.log('Registering API key:', key);
    try {
      const res = await postRequest(`${backendURL}/register`, { apiKey: key });
      console.log('Register response:', res);
      return res;
    } catch (e) {
      console.error('Register error:', e);
      return null;
    }
  }

  async function getRoles() {
    console.log('Fetching roles from backend...');
    try {
      const roles = await getRequest(`${backendURL}/roles`);
      console.log('Roles response:', roles);
      return roles;
    } catch (e) {
      console.error('Get roles error:', e);
      return null;
    }
  }

  async function getRooms() {
    try {
      const rooms = await getRequest(`${backendURL}/rooms`);
      return rooms;
    } catch (err) {
      console.error('Error fetching rooms:', err);
      return [];
    }
  }

  async function fetchUserDetails(apiKey) {
    try {
      const response = await fetch(`https://api.torn.com/user/?selections=profile&key=${apiKey}`);
      const data = await response.json();
      const name = data.name || "Unknown";
      const status = data.status?.description || data.status?.state || "Unknown";
      return { name, status };
    } catch (err) {
      console.error("Failed to fetch user details:", err);
      return { name: "Unknown", status: "Unknown" };
    }
  }

  async function pushStatusToBackend(apiKey, userName, status) {
    if (!apiKey) return;
    try {
      await postRequest(`${backendURL}/status/update`, { apiKey, userName, status });
      console.log(`Status pushed to backend: ${status}`);
    } catch (err) {
      console.error("Failed to push status to backend:", err);
    }
  }

  async function pollUserStatus() {
    const key = await getApiKey();
    if (!key) return;
    const { name, status } = await fetchUserDetails(key);
    await pushStatusToBackend(key, name, status);
    currentStatus = status;
    currentUserName = name;
  }

  async function createUI(role, status, rooms = [], userName = "") {
    console.log(`Creating UI with Role: ${role}, Status: ${status}`);
    let existing = document.getElementById("torn-war-tool-ui");
    if (existing) existing.remove();

    const container = document.createElement("div");
    container.id = "torn-war-tool-ui";
    container.style.position = "fixed";
    container.style.top = "20px";
    container.style.right = "20px";
    container.style.zIndex = "10000";
    container.style.background = "#1e1e2f";
    container.style.color = "#fff";
    container.style.padding = "16px 20px";
    container.style.borderRadius = "12px";
    container.style.boxShadow = "0 4px 12px rgba(0,0,0,0.3)";
    container.style.fontFamily = "'Segoe UI', sans-serif";
    container.style.width = "320px";
    container.style.cursor = "move";

    container.innerHTML = `
      <div id="torn-ui-drag" style="font-size: 16px; font-weight: 600; margin-bottom: 10px;">
        🔫 Torn War Tool
      </div>
      <div style="margin-bottom: 6px;"><strong>Role:</strong> <span style="color:#00ffff">${role}</span></div>
      <div style="margin-bottom: 12px;"><strong>Status:</strong> <span style="color:#90ee90">${status}</span></div>
      <div id="room-list" style="max-height: 200px; overflow-y: auto; background: #282c34; padding: 10px; border-radius: 8px; margin-bottom: 12px;">
        <strong>Rooms:</strong>
        ${rooms.length === 0 ? "<div>No rooms available</div>" : ""}
      </div>
      <div id="room-actions"></div>
    `;

    document.body.appendChild(container);
    makeDraggable(container);

    const roomList = container.querySelector("#room-list");
    const roomActions = container.querySelector("#room-actions");
    const apiKey = await getApiKey();

    rooms.forEach(room => {
      const roomDiv = document.createElement("div");
      roomDiv.style.border = "1px solid #444";
      roomDiv.style.padding = "8px";
      roomDiv.style.borderRadius = "6px";
      roomDiv.style.marginBottom = "8px";

      const roomTitle = document.createElement("div");
      roomTitle.textContent = room.name;
      roomTitle.style.fontWeight = "700";

      if (role === "Leadership") {
        const delBtn = document.createElement("button");
        delBtn.textContent = "Delete Room";
        delBtn.style.marginLeft = "8px";
        delBtn.style.background = "#d9534f";
        delBtn.style.color = "white";
        delBtn.style.border = "none";
        delBtn.style.padding = "4px 8px";
        delBtn.style.cursor = "pointer";
        delBtn.onclick = async () => {
          if (confirm(`Delete room "${room.name}"?`)) {
            try {
              const apiKeyVal = await getApiKey();
              await deleteRequest(`${backendURL}/rooms/${room.id}`, { apiKey: apiKeyVal });
              alert("Room deleted!");
              await sendApiKeyToBackend();
            } catch (err) {
              alert("Error deleting room: " + err.message);
            }
          }
        };
        roomTitle.appendChild(delBtn);
      }

      roomDiv.appendChild(roomTitle);

      if (!room.members || room.members.length === 0) {
        const noMembers = document.createElement("div");
        noMembers.textContent = "No members";
        noMembers.style.fontStyle = "italic";
        roomDiv.appendChild(noMembers);
      } else {
        const membersList = document.createElement("ul");
        room.members.forEach(m => {
          const memberItem = document.createElement("li");
          memberItem.textContent = `${m.userName} - ${m.status}`;
          membersList.appendChild(memberItem);
        });
        roomDiv.appendChild(membersList);
      }

      const userInRoom = room.members.some(m => m.apiKey === apiKey);

      const joinBtn = document.createElement("button");
      joinBtn.textContent = userInRoom ? "Leave Room" : "Join Room";
      joinBtn.style.marginTop = "8px";
      joinBtn.style.padding = "4px 10px";
      joinBtn.style.cursor = "pointer";
      joinBtn.onclick = async () => {
        const apiKeyVal = await getApiKey();
        if (!apiKeyVal) {
          alert("Please set your API key");
          return;
        }
        let userNameVal = userName;
        if (!userInRoom) {
          userNameVal = userName || prompt("Enter your in-game name:");
          if (!userNameVal) {
            alert("Name is required");
            return;
          }
        }
        try {
          if (userInRoom) {
            await postRequest(`${backendURL}/rooms/${room.id}/leave`, { apiKey: apiKeyVal });
            alert(`Left room "${room.name}"`);
          } else {
            const { status: freshStatus } = await fetchUserDetails(apiKeyVal);
            await postRequest(`${backendURL}/rooms/${room.id}/join`, {
              apiKey: apiKeyVal,
              userName: userNameVal,
              status: freshStatus
            });
            alert(`Joined room "${room.name}"`);
          }
          await sendApiKeyToBackend();
        } catch (err) {
          alert(err.message);
        }
      };
      roomDiv.appendChild(joinBtn);

      roomList.appendChild(roomDiv);
    });

    if (role === "Leadership") {
      const createRoomBtn = document.createElement("button");
      createRoomBtn.textContent = "Create Room";
      createRoomBtn.style.marginTop = "8px";
      createRoomBtn.style.padding = "6px 12px";
      createRoomBtn.style.cursor = "pointer";

      createRoomBtn.onclick = async () => {
        const name = prompt("Enter new room name:");
        if (name) {
          try {
            const apiKeyVal = await getApiKey();
            await postRequest(`${backendURL}/rooms`, { name, apiKey: apiKeyVal });
            alert("Room created!");
            await sendApiKeyToBackend();
          } catch (err) {
            console.error("Error creating room:", err);
            alert("Error creating room: " + err.message);
          }
        }
      };
      roomActions.appendChild(createRoomBtn);
    }
  }

  function makeDraggable(el) {
    const dragArea = el.querySelector("#torn-ui-drag");
    let offsetX = 0, offsetY = 0, isDragging = false;

    dragArea.addEventListener("mousedown", (e) => {
      isDragging = true;
      offsetX = e.clientX - el.offsetLeft;
      offsetY = e.clientY - el.offsetTop;
      document.body.style.userSelect = "none";
    });

    document.addEventListener("mousemove", (e) => {
      if (!isDragging) return;
      el.style.left = `${e.clientX - offsetX}px`;
      el.style.top = `${e.clientY - offsetY}px`;
      el.style.right = "auto";
    });

    document.addEventListener("mouseup", () => {
      isDragging = false;
      document.body.style.userSelect = "";
    });
  }

  async function sendApiKeyToBackend() {
    const key = await getApiKey();
    if (!key) {
      console.log('No API key provided, aborting.');
      createUI("No Key", "Please enter API key");
      return;
    }

    await registerApiKey(key);
    const users = await getRoles();
    if (!users) {
      createUI("Error", "Check backend");
      return;
    }

    const userEntry = users.find(u => u.apiKey === key);
    const role = userEntry ? userEntry.role : "Unknown";

    const rooms = await getRooms();

    const { name: fetchedName, status: fetchedStatus } = await fetchUserDetails(key);

    await createUI(role, fetchedStatus, rooms, fetchedName);
  }

  let currentStatus = "Unknown";
  let currentUserName = "Unknown";

  pollUserStatus();
  sendApiKeyToBackend();
  setInterval(pollUserStatus, 10000);
  setInterval(sendApiKeyToBackend, 15000);
})();