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