🌺 🐫 YATA Travel Stock — Flowers, Plushies, Xanax (Above PDA)

Shows foreign stock (flowers, plushies, and Xanax in SA) from YATA export. Refreshes every 20s. Above PDA.

Από την 11/10/2025. Δείτε την τελευταία έκδοση.

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey, το Greasemonkey ή το Violentmonkey για να εγκαταστήσετε αυτόν τον κώδικα.

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey ή το Violentmonkey για να εγκαταστήσετε αυτόν τον κώδικα.

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey ή το Violentmonkey για να εγκαταστήσετε αυτόν τον κώδικα.

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey ή το Userscripts για να εγκαταστήσετε αυτόν τον κώδικα.

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

Θα χρειαστεί να εγκαταστήσετε μια επέκταση διαχείρισης κώδικα χρήστη για να εγκαταστήσετε αυτόν τον κώδικα.

(Έχω ήδη έναν διαχειριστή κώδικα χρήστη, επιτρέψτε μου να τον εγκαταστήσω!)

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.

(Έχω ήδη έναν διαχειριστή στυλ χρήστη, επιτρέψτε μου να τον εγκαταστήσω!)

// ==UserScript==
// @name         🌺 🐫 YATA Travel Stock — Flowers, Plushies, Xanax (Above PDA)
// @namespace    http://tampermonkey.net/
// @version      1.2.0
// @description  Shows foreign stock (flowers, plushies, and Xanax in SA) from YATA export. Refreshes every 20s. Above PDA.
// @author       Nova
// @match        https://www.torn.com/page.php?sid=travel*
// @grant        GM_addStyle
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_xmlhttpRequest
// @connect      yata.yt
// @run-at       document-end
// ==/UserScript==

(function() {
  'use strict';
  if (!/page\.php\?sid=travel/.test(location.href)) return;

  const YATA_URL = 'https://yata.yt/api/v1/travel/export/';
  const POLL_MS = 20 * 1000;

  const FLOWERS = [
    "Dahlia", "Orchid", "African Violet", "Cherry Blossom",
    "Peony", "Ceibo Flower", "Edelweiss", "Crocus",
    "Heather", "Tribulus Omanense", "Banana Orchid"
  ];

  const PLUSHIES = [
    "Sheep Plushie", "Teddy Bear Plushie", "Kitten Plushie",
    "Jaguar Plushie", "Wolverine Plushie", "Nessie Plushie",
    "Red Fox Plushie", "Monkey Plushie", "Chamois Plushie",
    "Panda Plushie", "Lion Plushie", "Camel Plushie",
    "Stingray Plushie"
  ];

  const TRACKED = new Set([...FLOWERS, ...PLUSHIES, "Xanax"]);

  const COUNTRY_NAMES = {
    mex: 'Mexico',
    cay: 'Cayman Islands',
    can: 'Canada',
    haw: 'Hawaii',
    uni: 'United Kingdom',
    arg: 'Argentina',
    swi: 'Switzerland',
    jap: 'Japan',
    chi: 'China',
    uae: 'UAE',
    sou: 'South Africa'
  };

  function getPDANavHeight() {
    const nav = document.querySelector('#pda-nav') || document.querySelector('.pda');
    return nav ? nav.offsetHeight : 40;
  }

  GM_addStyle(`
    #yataStockPanel {
      position: fixed;
      top: ${getPDANavHeight()}px;
      left: 18px;
      width: 320px;
      background: #080808;
      color: #e9eef6;
      font-family: "DejaVu Sans Mono", monospace;
      font-size: 11px;
      border: 1px solid #333;
      border-radius: 6px;
      z-index: 999999;
      box-shadow: 0 6px 18px rgba(0,0,0,0.6);
      max-height: 70vh;
      overflow-y: auto;
      line-height: 1.1;
      padding-bottom:6px;
    }
    #yataHeader {
      background:#101010;
      padding:6px 8px;
      cursor:pointer;
      font-weight:700;
      font-size:12px;
      border-bottom:1px solid #2b2b2b;
      user-select:none;
    }
    #yataContent { padding:8px; display:block; }
    .yata-controls { margin-bottom:8px; }
    .yata-controls button {
      margin:2px 6px 6px 0;
      font-size:11px;
      padding:4px 8px;
      background:#121212;
      color:#e9eef6;
      border:1px solid #2b2b2b;
      border-radius:4px;
      cursor:pointer;
    }
    .yata-controls button:hover { background:#1b1b1b; }
    #yataStatus { font-size:11px; color:#bdbdbd; margin-bottom:8px; }
    .country-block { margin-bottom:8px; border-top:1px dashed #222; padding-top:6px; }
    .country-title { font-weight:700; margin-bottom:4px; display:flex; justify-content:space-between; align-items:center; }
    .country-title .ct-left { font-size:12px; }
    .country-upd { font-size:10px; color:#9ea6b3; }
    .item-row { display:flex; justify-content:space-between; gap:8px; padding:2px 0; align-items:center; }
    .item-name { flex:1 1 auto; min-width:0; overflow:hidden; text-overflow:ellipsis; }
    .qty { width:64px; text-align:right; font-weight:700; }
    .cost { width:56px; text-align:right; color:#aeb7c4; font-size:11px; }
    .status-dot { width:10px; height:10px; border-radius:50%; display:inline-block; margin-right:6px; vertical-align:middle; }
    .dot-green { background:#00c853; }
    .dot-yellow { background:#ffb300; }
    .dot-red { background:#ff1744; }
    .small-note { font-size:11px; color:#9ea6b3; margin-top:6px; }
  `);

  const panel = document.createElement('div');
  panel.id = 'yataStockPanel';
  panel.innerHTML = `
    <div id="yataHeader">▶ 🌺 🐫 YATA Stock (Flowers & Plushies)</div>
    <div id="yataContent">
      <div class="yata-controls">
        <button id="yata_refresh">Refresh</button>
        <button id="yata_toggle_all">Collapse</button>
      </div>
      <div id="yataStatus">Loading...</div>
      <div id="yataList"></div>
      <div class="small-note">Data from yata.yt · flowers, plushies, and Xanax (SA) only · refresh 20s</div>
    </div>
  `;
  document.body.appendChild(panel);

  const header = panel.querySelector('#yataHeader');
  const content = panel.querySelector('#yataContent');
  const statusEl = panel.querySelector('#yataStatus');
  const listEl = panel.querySelector('#yataList');
  const btnRefresh = panel.querySelector('#yata_refresh');
  const btnToggle = panel.querySelector('#yata_toggle_all');

  let collapsed = GM_getValue('yata_collapsed', false);
  function updateCollapseUI() {
    content.style.display = collapsed ? 'none' : 'block';
    header.textContent = (collapsed ? '▶' : '▼') + ' 🌺 🐫 YATA Stock (Flowers & Plushies)';
    btnToggle.textContent = collapsed ? 'Expand' : 'Collapse';
    GM_setValue('yata_collapsed', collapsed);
  }
  updateCollapseUI();

  header.addEventListener('click', () => { collapsed = !collapsed; updateCollapseUI(); });
  btnToggle.addEventListener('click', () => { collapsed = !collapsed; updateCollapseUI(); });
  btnRefresh.addEventListener('click', () => fetchAndRender(true));

  function qtyClass(q) {
    if (q <= 0) return 'dot-red';
    if (q <= 10) return 'dot-yellow';
    return 'dot-green';
  }

  function gmFetchJson(url) {
    return new Promise((resolve, reject) => {
      GM_xmlhttpRequest({
        method: 'GET',
        url,
        responseType: 'json',
        onload: (res) => {
          let data = res.response;
          if (!data && res.responseText) {
            try { data = JSON.parse(res.responseText); } catch(e){}
          }
          if (!data) reject(new Error('No JSON'));
          else resolve(data);
        },
        onerror: reject
      });
    });
  }

  function renderExport(data) {
    if (!data || !data.stocks) {
      statusEl.textContent = 'No stock data.';
      listEl.innerHTML = '';
      return;
    }

    const ts = data.timestamp ? new Date(data.timestamp * 1000) : null;
    statusEl.textContent = ts ? `Last payload: ${ts.toUTCString()}` : 'Live data';

    let html = '';
    Object.keys(data.stocks).forEach(code => {
      const c = data.stocks[code];
      if (!c) return;
      const name = COUNTRY_NAMES[code] || code.toUpperCase();
      const upd = c.update ? new Date(c.update * 1000) : null;

      const items = Array.isArray(c.stocks) ? c.stocks.filter(it => {
        if (TRACKED.has(it.name)) {
          if (it.name === "Xanax") return code === "sou"; // only SA
          return true;
        }
        return false;
      }) : [];

      if (!items.length) return;

      html += `<div class="country-block" data-country="${code}">
        <div class="country-title">
          <div class="ct-left">${name} <span class="country-upd">${upd ? upd.toUTCString() : ''}</span></div>
          <div class="ct-right">${code}</div>
        </div>`;

      items.forEach(it => {
        const q = Number(it.quantity ?? 0);
        const cost = it.cost != null ? Number(it.cost) : null;
        const dot = `<span class="status-dot ${qtyClass(q)}"></span>`;
        html += `<div class="item-row">
          <div class="item-name">${dot}${escapeHtml(it.name)}</div>
          <div class="cost">${cost != null ? formatNumber(cost) : '-'}</div>
          <div class="qty">${q}</div>
        </div>`;
      });
      html += `</div>`;
    });

    listEl.innerHTML = html || '<div style="color:#999;">No tracked items found.</div>';
  }

  function escapeHtml(s) {
    return s ? s.replace(/[&<>"']/g, m => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[m])) : '';
  }

  function formatNumber(n) {
    return n.toLocaleString('en-US');
  }

  let pollHandle = null;
  let lastPayloadTimestamp = 0;

  async function fetchAndRender(force=false) {
    try {
      statusEl.textContent = 'Fetching YATA export...';
      const data = await gmFetchJson(YATA_URL);
      if (!force && data.timestamp === lastPayloadTimestamp) return;
      lastPayloadTimestamp = data.timestamp;
      renderExport(data);
    } catch (err) {
      statusEl.textContent = 'Fetch error: ' + (err.message || err);
      listEl.innerHTML = '';
    }
  }

  function startPolling() {
    if (pollHandle) return;
    fetchAndRender(true);
    pollHandle = setInterval(() => fetchAndRender(false), POLL_MS);
  }

  startPolling();
  window.addEventListener('beforeunload', () => clearInterval(pollHandle));

})();