Unified Stock (Smart Predictive)

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

Versión del día 11/10/2025. Echa un vistazo a la versión más reciente.

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