Display hospital timer on the attack page
// ==UserScript==
// @name torn-attack-hospital-timer
// @namespace typhon.torn.attack-hospital
// @version 1.5.3
// @description Display hospital timer on the attack page
// @author rantMore [3265877]
// @license GNU GPLv3
// @run-at document-end
// @license MIT
// @grant GM_log
// @grant GM_addStyle
// @match https://www.torn.com/loader.php?sid=attack*
// @match https://www.torn.com/page.php?sid=attack*
// ==/UserScript==
const apiKey = "YOUR_API_KEY_HERE";
// ## DO NOT EDIT FROM THIS POINT ON ##
const TAHT_VERSION = '1.5.3';
const divContainer = '.dialog___Q0GdI';
const divTopSection = '.topSection___U7sVi';
const initialTitle = document.title;
const userid = location.href.replace(/.*?user2ID=(\d+)/i, "$1");
let userFactionid = 0;
let factionInformation = {};
let userInformation = {};
let singleton = document.getElementById("taht-scouter-run-once");
if (!singleton) {
console.log(`[Torn Attack Hospital Timer] Version ${TAHT_VERSION} starting`);
GM_addStyle(`
.timer-indicator {
padding: 10px 10px;
background-color: #000000;
font-weight: 700;
font-size: 1.2rem;
text-align: center;
color: var(--attack-dialog-red-title)
}
.topFactionSection {
display: flex;
flex-direction: row;
flex-wrap: wrap;
gap: 15px;
justify-content: flex-end;
font-size: large;
}
.info-value-yes {
color: green;
}
.info-value-no {
color: red;
}
`)
}
function generateInformation(outTime, hospital_timestamp) {
// Show green under 1 minute
const seconds = Math.floor((outTime - Date.now()) / 1000);
return `
<div> </div>
<div>Coming out at ${outTime.toLocaleTimeString('en-US')} in</div>
<div class="timer-indicator"><span style="${seconds < 120 ? 'color: #98FB98' : ''}">${getTimeLeft(hospital_timestamp)}</span></div>
`;
}
function getTimeLeft(hospital_timestamp) {
let outTime = new Date(0);
outTime.setUTCSeconds(hospital_timestamp);
const seconds = Math.floor((outTime - Date.now()) / 1000);
if (seconds >= 60) {
const secondsFormated = Math.floor(seconds % 60).toString().padStart(2, '0');
return seconds < 3600 ? `${Math.floor(seconds/60)}:${secondsFormated}` : `${Math.floor(seconds / 3600)}h${Math.floor((seconds % 3600) / 60).toString().padStart(2, '0')}`;
} else {
return `${seconds}s`;
}
}
function fetch_FactionInfo() {
return new Promise( (resolve, reject) => {
if (userFactionid) {
fetch(`https://api.torn.com/faction/${userFactionid}/wars?key=${apiKey}&comment=attack_stats`).then( async response => {
factionInformation = (await response.json())||{};
resolve(factionInformation);
})
} else {
resolve(undefined);
}
});
}
function fetch_UserInfo() {
return new Promise( (resolve, reject) => {
if (userFactionid) {
fetch(`https://api.torn.com/user/${userid}?selections=profile&key=${apiKey}&comment=attack_stats`).then( async response => {
userInformation = (await response.json())||{};
updateUserInfo();
resolve(userInformation);
})
} else {
resolve(undefined);
}
});
}
function updateUserInfo() {
if (userInformation) {
let statusDiv = document.querySelector('.topFactionSection #lastAction-info');
statusDiv.innerHTML = `Last action: <span class="info-value">${userInformation.last_action.relative}</span>`;
statusDiv = document.querySelector('.topFactionSection #online-info');
statusDiv.innerHTML = `Online: <span class="info-value-${ userInformation.last_action.status === 'Online' ? 'yes' : 'no'}">${ userInformation.last_action.status === 'Online' ? 'Yes' : 'No'}</span>`;
}
}
(function hospital_time() {
'use strict';
fetch(`https://api.torn.com/user/${userid}?selections=profile&key=${apiKey}&comment=attack_stats`).then( async response => {
let user = (await response.json())||{};
userInformation = user;
// Grab the top section and add our div
let outerBox = document.querySelector(divTopSection);
if (outerBox) {
let divExtraWrapper = document.createElement("div")
divExtraWrapper.setAttribute("class", "topFactionSection");
divExtraWrapper.innerHTML = `
<div id="war-info">War: <span class="info-value-no">N/A</span></div>
<div id="tt-info">Territory: <span class="info-value-no">N/A</span></div>
<div id="lastAction-info">Territory: <span class="info-value-no">N/A</span></div>
<div id="online-info">Online: <span class="info-value-no">N/A</span></div>
`;
outerBox.after(divExtraWrapper);
updateUserInfo();
}
// If user is in faction, grab the information
if (user?.faction) {
userFactionid = user.faction.faction_id || 0;
fetch_FactionInfo().then( (faction) => {
if (faction) {
// Grab top section box, we'll add it next to it
let outerBox = document.querySelector(divTopSection);
if (outerBox) {
const wars = Object.entries(faction.ranked_wars||{})[0];
const tts = Object.entries(faction.territory_wars||{})[0];
const isInWar = wars && wars[0] ? true : false;
const isInTT = tts && tts[0] ? true : false;
let statusDiv = document.querySelector('.topFactionSection #war-info');
if (statusDiv) {
statusDiv.innerHTML = `War: <span class="info-value-${ isInWar ? 'yes' : 'no'}">${ isInWar ? 'Yes' : 'No'}</span>`;
}
statusDiv = document.querySelector('.topFactionSection #tt-info');
if (statusDiv) {
statusDiv.innerHTML = `Territory: <span class="info-value-${ isInTT ? 'yes' : 'no'}">${ isInTT ? 'Yes' : 'No'}</span>`;
}
} else {
// What to do???
}
} else {
// no faction??
}
})
}
if (user.status.state.toLowerCase() === 'hospital') {
let alertWrapper = document.createElement("div")
alertWrapper.setAttribute("class", "userName___loAWK bold");
let outTime = new Date(0); // The 0 there is the key, which sets the date to the epoch
outTime.setUTCSeconds(user.states.hospital_timestamp);
// Don't do anything if we are right on the ending of the hospital timer
let msToWait = outTime - Date.now();
// Prepare div content
alertWrapper.innerHTML = generateInformation(outTime, user.states.hospital_timestamp);
document.title = `${getTimeLeft(user.states.hospital_timestamp)} | ${user.name}`;
// Sometimes the element is not yet present, let's wait for it
let outerBox = document.querySelector(divContainer);
let timerPtr;
let tempBox;
let tempDiv;
new Promise( (resolve, reject) => {
if (!outerBox) {
// Lets grab the top div to add the time info insde. If it fails, then too bad for the tentative
tempBox = document.querySelector('.colored___sN72G');
if (tempBox) {
tempDiv = tempBox.appendChild(alertWrapper);
}
let waitPtr = setInterval( () => {
document.title = `${getTimeLeft(user.states.hospital_timestamp)} | ${user.name}`;
outerBox = document.querySelector(divContainer);
if (outerBox) {
resolve(true)
clearInterval(waitPtr);
}
}, 250)
} else {
resolve(true)
}
}).then( () => {
// Good to go!
if (tempDiv) {
// lets get rid of this extra div
tempDiv.remove()
}
outerBox.appendChild(alertWrapper);
// Redraw content every second
timerPtr = setInterval( () => {
alertWrapper.innerHTML = generateInformation(outTime, user.states.hospital_timestamp);
document.title = `${getTimeLeft(user.states.hospital_timestamp)} | ${user.name}`;
}, 1000);
// Check status periodically
setInterval( () => {
fetch_UserInfo().then( (user) => {
//console.log('fetched user', user);
});
}, 5000)
})
// Set a new timer to trigger when we are done waiting
setTimeout( () => {
// Stop everything, we are done.
document.title = initialTitle;
clearInterval(timerPtr);
}, msToWait);
}
});
})();