Unified Stock (Smart Predictive)

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

Versione datata 11/10/2025. Vedi la nuova versione l'ultima versione.

Dovrai installare un'estensione come Tampermonkey, Greasemonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Userscripts per installare questo script.

Dovrai installare un'estensione come ad esempio Tampermonkey per installare questo script.

Dovrai installare un gestore di script utente per installare questo script.

(Ho già un gestore di script utente, lasciamelo installare!)

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

(Ho già un gestore di stile utente, lasciamelo installare!)

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