8 Commits

Author SHA1 Message Date
TheCappn bd7cfef2ee Moved code around. Began YATA Foreign Stock Testing. 2026-07-08 01:51:30 -04:00
TheCappn 2c7977a715 Update travel-time/travel-helper.js
Updated CSS for appraisal info.
2026-07-07 13:20:58 -04:00
TheCappn a25d8e4012 Progress on appraisal UI 2026-07-07 09:41:59 -04:00
TheCappn 97d1d0d2f4 Update version number 2026-07-07 02:19:52 -04:00
TheCappn ae4e841643 Fixed formatting issue 2026-07-07 02:19:13 -04:00
TheCappn 1883d5a594 Added basic element for travel item appraisal. 2026-07-07 01:42:48 -04:00
TheCappn f32d74eed4 Updated README 2026-07-06 11:35:26 -04:00
TheCappn e88905d4df Removed old code 2026-07-06 11:28:56 -04:00
2 changed files with 200 additions and 60 deletions
+9
View File
@@ -1,2 +1,11 @@
# Torn-Userscripts
## A script to help with trading/traveling
### Features
- Calculates your landing time while traveling, prints it in your local time
- Removes useless travel animation and fact wrapper
### Planned features
- Enumerate your travel inventory and provide a value based on W3B market data
- Show stock information about your travel destination
+191 -60
View File
@@ -1,11 +1,12 @@
// ==UserScript==
// @name Torn - Arrival Time Predictor
// @namespace http://tampermonkey.net/
// @version 1.02
// @version 1.04
// @description Calculates and displays the exact local time you will land while traveling
// @author You
// @match https://www.torn.com/page.php?sid=travel
// @grant GM_addStyle
// @grant GM_xmlhttpRequest
// ==/UserScript==
(function() {
@@ -15,7 +16,7 @@
GM_addStyle(`
@keyframes colorPulse {
0% { color: #ff3333; } /* Start Red */
50% { color: #ffffff; } /* Midpoint Orange */
50% { color: #ffffff; } /* Midpoint White */
100% { color: #ff3333; } /* End Red */
}
@@ -23,10 +24,31 @@
animation: colorPulse 1s infinite ease-in-out;
font-size: 2rem;
}
.appraise-info {
border-radius: 30px;
border: 2px solid #777;
padding: 4px 8px;
background: #222;
margin: 5px;
display: inline-block;
}
`);
//const flight_image = document.querySelector("#travel-root > div.viewport___cdEK9 > div.airspaceScene___euCKY.outboundFlight___rIvIA");
//flight_image.remove();
// Get destination
const destination = document.querySelector("body").getAttribute("data-country");
const destShortCode = destination.substring(0,3);
console.log(`Destination detected: ${destination}. Short Code: ${destShortCode}`);
/* TODO: Fix the table issue in getForeignStock
if (destination != "torn"){
getForeignStock(destShortCode);
}
*/
// Setup Formatter
const currencyFormatter = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
maximumFractionDigits: 0,
});
// 1. Wait for Torn's travel container to load
const checkFlight = setInterval(() => {
@@ -40,65 +62,174 @@
}, 500);
function initClockExtension(timerElement) {
// Create a custom UI box to display our text safely without breaking Torn's CSS
const travelRoot = document.querySelector("#travel-root");
const parentContainer = travelRoot.querySelector('[class^="progressText"]');
// Remove travel fact
travelRoot.querySelector('[class^="factWrapper"]').remove();
// Remove flight scene
travelRoot.querySelector('[class^="airspaceScene"]').remove();
const ogTimer = document.querySelector("#travel-root > div.viewport___cdEK9 > div.flightProgressSection___OEJoR > div.progressText___BEQ0k > div > span");
var timerContainer = null;
/* if (ogTimer) {
ogTimer.style.cssText = 'font-weight: bold; font-size: 1.5rem;';
ogTimer.innerText = 'Calculating landing time...';
timerContainer = ogTimer;
}
*/
const timeDisplay = document.createElement('div');
timeDisplay.style.cssText = 'padding: 10px; margin-top: 10px; color: #fff; text-align: center; border-radius: 5px; font-weight: bold; border: 1px solid #444; transition: color 0.5s ease; font-weight: bold; font-size: 1.5rem;';
//timerElement.parentNode.insertBefore(timeDisplay, timerElement.nextSibling);
parentContainer.insertAdjacentElement('beforebegin', timeDisplay);
timeDisplay.innerText = 'Calculating landing time...';
timerContainer = timeDisplay;
//const info = document.querySelector("#travel-root > div.viewport___cdEK9.stage0 > div.factWrapper___LvERA > div");
//info.remove();
// Create a custom UI box to display our text safely without breaking Torn's CSS
const travelRoot = document.querySelector("#travel-root");
const parentContainer = travelRoot.querySelector(
'[class^="progressText"]',
);
// Remove travel fact
travelRoot.querySelector('[class^="factWrapper"]').remove();
// Remove flight scene
travelRoot.querySelector('[class^="airspaceScene"]').remove();
const ogTimer = document.querySelector(
"#travel-root > div.viewport___cdEK9 > div.flightProgressSection___OEJoR > div.progressText___BEQ0k > div > span",
);
var timerContainer = null;
const timeDisplay = document.createElement("div");
timeDisplay.style.cssText = "padding: 10px; margin-top: 10px; color: #fff; text-align: center; font-weight: bold; transition: color 0.5s ease; font-weight: bold; font-size: 1.5rem;";
parentContainer.insertAdjacentElement("beforebegin", timeDisplay);
timeDisplay.innerText = "Calculating landing time...";
timerContainer = timeDisplay;
// Insert our box right below Torn's native countdown timer
//
// Update the landing time prediction every single second
setInterval(() => {
const timeString = timerElement.innerText; // Grabs 'HH:MM:SS' from the page
if (!timeString) return;
const timeParts = timeString.split(':');
if (timeParts.length === 3) {
const hours = parseInt(timeParts[0], 10);
const minutes = parseInt(timeParts[1], 10);
const seconds = parseInt(timeParts[2], 10);
// Convert countdown duration entirely to total milliseconds
const msRemaining = ((hours * 60 * 60) + (minutes * 60) + seconds) * 1000;
// Blink the text if time left is < 2 minutes.
if (msRemaining <= 120000) {
//ogTimer.style.cssText += ' color: #aa0000;'
// Add the blinking class to the text.
timerContainer.classList.add('pulse-text');
// Enumerate Inventory items
var travelInventory = travelRoot.querySelectorAll('[class^="itemButton"]');
var iteminfo = [];
if (travelInventory.length > 0) {
travelInventory.forEach((item) => {
let itemImg = item.querySelector("img"); // Use the image URL to get the item ID
if (itemImg) {
let splitUrl = itemImg.src.split("/"); // Split the URL by the forward slashes
const id = splitUrl[5]; // The 6th element is the Item ID
var itemIndex = iteminfo.findIndex(item => item.id === id);
if (itemIndex === -1) {
itemIndex = iteminfo.push( { id: id, qty: 0, item_name: "", market: 0, bazaar_min: 0, bazaar_average: 0}) - 1;
}
// Add the flight duration to the current system date/time
const landingTime = new Date(Date.now() + msRemaining);
// Remove OG timer
//ogTimer.remove();
// Format the output neatly (e.g., "16:24:05")
timerContainer.innerText = `Estimated Landing: ${landingTime.toLocaleTimeString()}`;
iteminfo[itemIndex].qty++;
}
}, 1000);
});
iteminfo.forEach(item => {
getPriceInfo(item.id);
});
}
// Update the landing time prediction every single second
setInterval(() => {
const timeString = timerElement.innerText; // Grabs 'HH:MM:SS' from the page
if (!timeString) return;
const timeParts = timeString.split(":");
if (timeParts.length === 3) {
const hours = parseInt(timeParts[0], 10);
const minutes = parseInt(timeParts[1], 10);
const seconds = parseInt(timeParts[2], 10);
// Convert countdown duration entirely to total milliseconds
const msRemaining = (hours * 60 * 60 + minutes * 60 + seconds) * 1000;
// Blink the text if time left is < 2 minutes.
if (msRemaining <= 120000) {
// Add the blinking class to the text.
timerContainer.classList.add("pulse-text");
}
// Add the flight duration to the current system date/time
const landingTime = new Date(Date.now() + msRemaining);
// Format the output neatly (e.g., "16:24:05")
timerContainer.innerText = `Landing in ${destination.charAt(0).toUpperCase() + destination.slice(1)} at ${landingTime.toLocaleTimeString()}`;
}
}, 1000);
}
// Function to update the page with price info
function updatePriceInfo() {
var values = {
market: 0,
bazaar_average: 0,
bazaar_min: 0
};
const invPanel = travelRoot.querySelector('[class^="inventoryPanel"]');
const appraisePanel = document.createElement("div");
appraisePanel.style.cssText = "color: #0c0; font-weight: bold; font-size: 1rem; text-align: center; background: #333; padding: 5px;"
invPanel.parentNode.insertBefore(appraisePanel, invPanel.nextSibling); // Add the new element after the travel inventory panel
iteminfo.forEach(item => { // Keep track of all totals for display
console.log(`Market Value of ${item.qty} ${item.item_name}: ${item.market * item.qty}. Bazaar Avg: ${item.bazaar_average * item.qty}. Bazaar Min: ${item.bazaar_min * item.qty}`);
values.bazaar_min = item.bazaar_min * item.qty;
values.bazaar_average = item.bazaar_average * item.qty;
values.market = item.market * item.qty;
});
// Update UI Elements
let bazMin = document.createElement("span");
bazMin.classList.add('appraise-info');
bazMin.innerText = `Bazaar Min: ${currencyFormatter.format(values.bazaar_min)}`;
let marketVal = document.createElement("span");
marketVal.classList.add('appraise-info');
marketVal.innerText = `Market: ${currencyFormatter.format(values.market)}`;
let bazAvg = document.createElement("span");
bazAvg.classList.add("appraise-info");
bazAvg.innerText = `Bazaar Avg: ${currencyFormatter.format(values.bazaar_average)}`;
appraisePanel.append(marketVal);
appraisePanel.append(bazMin);
appraisePanel.append(bazAvg);
//appraisePanel.innerText = `Quick Sell: ${values.bazaar_min} | Market: ${values.market} | Bazaar Avg: ${values.bazaar_average}`;
}
function updateForeignStock(stockInfo) {
const progressContainer = document.querySelector('[class^="flightProgressSection"]');
const infoContainer = document.createElement("div");
let infoTable = document.createElement("table");
//infoContainer.append(infoTable);
let tableHeader = document.createElement("tr");
tableHeader.append(document)
tableHeader.createElement("th").innerText = "Item";
tableHeader.createElement("th").innerText = "Quantity";
tableHeader.createElement("th").innerText = "Price";
stockInfo.stocks.forEach(item => {
let newRow = infoTable.createElement("tr");
newRow.createElement("td").innerText = `${item.name}`;
newRow.createElement("td").innerText = `${item.quantity}`;
newRow.createElement("td").innerText = `${currencyFormatter.format(item.cost)}`;
//let infoContainer = document.createElement("span");
//infoContainer.innerText = `${item.name} | ${item.quantity} | ${item.cost}`;
progressContainer.parentNode.insertBefore(infoContainer, progressContainer.nextSibling);
})
}
// Get YATA Foreign stock information
function getForeignStock(country) {
var stockInfo = {};
GM_xmlhttpRequest({
method: "GET",
url: "https://yata.yt/api/v1/travel/export/",
//headers: {"Content-Type": "application/json" },
onload: function(response) {
if (response.status === 200) {
var data = JSON.parse(response.responseText);
stockInfo = data.stocks[country];
updateForeignStock(stockInfo);
//console.log(stockInfo);
} else {
console.log(`YATA Error ${response.status} - ${response.responseText}`);
}
}
});
return stockInfo;
}
// Function to get price information
function getPriceInfo(itemId) {
const w3bPriceUrl = "https://weav3r.dev/api/marketplace/" + itemId; // API Endpoint for W3B Market Information
var data = null;
GM_xmlhttpRequest({
method: "GET",
url: w3bPriceUrl,
headers: { "Content-Type": "application/json" },
onload: function(response) {
if (response.status === 200) {
data = JSON.parse(response.responseText);
// Start updating the item's information.
let index = iteminfo.findIndex(item => item.id === itemId);
iteminfo[index].item_name = data.item_name;
iteminfo[index].market = data.market_price;
iteminfo[index].bazaar_min = data.listings[0].price;
iteminfo[index].bazaar_average = data.bazaar_average;
updatePriceInfo(); // Update the UI
}
}
});
}
})();