Unified Stock (Smart Predictive)

Predicts foreign stock depletion/restock before landing using YATA travel data.

As of 11.10.2025. See ბოლო ვერსია.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         Unified Stock (Smart Predictive)
// @namespace    https://yata.alwaysdata.net/
// @version      3.0
// @description  Predicts foreign stock depletion/restock before landing using YATA travel data.
// @author       You
// @match        https://www.torn.com/*
// @grant        GM_xmlhttpRequest
// @connect      yata.alwaysdata.net
// ==/UserScript==

(function () {
  'use strict';

  const API_BASE = 'https://yata.alwaysdata.net/api/v1';
  const REFRESH_INTERVAL = 5 * 60 * 1000; // 5 minutes
  let unifiedStock = {};
  let travelTimes = {};

  // ----------- HELPERS -----------
  const sleep = ms => new Promise(res => setTimeout(res, ms));

  async function fetchJSON(url) {
    return new Promise((resolve, reject) => {
      GM_xmlhttpRequest({
        method: 'GET',
        url,
        onload: res => {
          try {
            resolve(JSON.parse(res.responseText));
          } catch (e) {
            reject(e);
          }
        },
        onerror: err => reject(err)
      });
    });
  }

  function timeNow() {
    return new Date().toLocaleTimeString();
  }

  function getFlightTime(countryCode) {
    return travelTimes[countryCode.toLowerCase()]?.duration || 45 * 60 * 1000; // fallback 45 min
  }

  function predictColor(item, countryCode) {
    if (!item || !countryCode) return 'grey';
    const now = Date.now();
    const flightMs = getFlightTime(countryCode);
    const arrival = now + flightMs;

    const stock = item.stock;
    const deplete = new Date(item.depletion).getTime();
    const restock = new Date(item.restock).getTime();

    // 🟢 Still available when arriving
    if (stock > 0 && (isNaN(deplete) || deplete > arrival)) return 'green';
    // 🔴 Will deplete before you land
    if (stock > 0 && deplete <= arrival) return 'red';
    // 🟠 Restocking before arrival
    if (stock === 0 && restock <= arrival) return 'orange';
    // 🔴 Otherwise empty
    return 'red';
  }

  // ----------- MAIN UI -----------
  function createPanel() {
    const panel = document.createElement('div');
    panel.id = 'unifiedStockPanel';
    panel.style = `
      position: fixed;
      top: 70px;
      left: 10px;
      background: rgba(20,20,20,0.95);
      color: #fff;
      font-size: 12px;
      padding: 8px 10px;
      border-radius: 10px;
      z-index: 9999;
      max-height: 80vh;
      overflow-y: auto;
      box-shadow: 0 0 8px rgba(0,0,0,0.5);
    `;
    panel.innerHTML = `
      <div style="font-weight:bold; margin-bottom:5px;">🌍 Unified Stock</div>
      <div id="usBody">Loading...</div>
      <button id="usRefresh" style="margin-top:5px;width:100%;">Refresh</button>
      <div style="margin-top:5px;font-size:10px;color:#ccc;" id="usTime"></div>
    `;
    document.body.appendChild(panel);
    document.getElementById('usRefresh').addEventListener('click', updateStock);
  }

  async function updateStock() {
    const body = document.getElementById('usBody');
    const time = document.getElementById('usTime');
    if (!body) return;

    body.textContent = 'Updating...';
    try {
      unifiedStock = await fetchJSON(`${API_BASE}/foreignstock/`);
      travelTimes = await fetchJSON(`${API_BASE}/travel/`);
    } catch (e) {
      body.textContent = '⚠️ Failed to fetch YATA data.';
      return;
    }

    let html = '';
    Object.entries(unifiedStock).forEach(([country, items]) => {
      html += `<div style="margin:3px 0;font-weight:bold;">${country.toUpperCase()}</div>`;
      items.forEach(item => {
        const color = predictColor(item, country);
        html += `
          <div style="display:flex;justify-content:space-between;padding:2px 0;border-bottom:1px solid #333;">
            <span style="color:${color};">●</span>
            <span>${item.name}</span>
            <span style="color:#999;">${item.stock}</span>
          </div>`;
      });
    });

    body.innerHTML = html;
    time.textContent = `Updated: ${timeNow()}`;
  }

  // ----------- INIT -----------
  async function init() {
    createPanel();
    await updateStock();
    setInterval(updateStock, REFRESH_INTERVAL);
  }

  // Wait for Torn DOM
  const checkInterval = setInterval(() => {
    if (document.readyState === 'complete') {
      clearInterval(checkInterval);
      init();
    }
  }, 1000);
})();