🌺 🐫 Points Exporter (Above PDA) — Works While Flying

Travel helper for TornPDA: exports flower/plushie points, works during flights by using the "items" API selection. Merges all sources automatically.

目前為 2025-10-09 提交的版本,檢視 最新版本

// ==UserScript==
// @name         🌺 🐫 Points Exporter (Above PDA) — Works While Flying
// @namespace    http://tampermonkey.net/
// @version      3.5.0
// @description  Travel helper for TornPDA: exports flower/plushie points, works during flights by using the "items" API selection. Merges all sources automatically.
// @author       Nova
// @match        https://www.torn.com/page.php?sid=travel*
// @grant        GM_addStyle
// @grant        GM_getValue
// @grant        GM_setValue
// ==/UserScript==

(function() {
  'use strict';

  if (!/page\.php\?sid=travel/.test(location.href)) return;

  // --- ITEMS LISTS ---
  const FLOWERS = {
    "Dahlia": "MX 🇲🇽", "Orchid": "HW 🏝️", "African Violet": "SA 🇿🇦",
    "Cherry Blossom": "JP 🇯🇵", "Peony": "CN 🇨🇳", "Ceibo Flower": "AR 🇦🇷",
    "Edelweiss": "CH 🇨🇭", "Crocus": "CA 🇨🇦", "Heather": "UK 🇬🇧",
    "Tribulus Omanense": "AE 🇦🇪", "Banana Orchid": "KY 🇰🇾"
  };

  const PLUSHIES = {
    "Sheep Plushie": "B.B 🏪", "Teddy Bear Plushie": "B.B 🏪", "Kitten Plushie": "B.B 🏪",
    "Jaguar Plushie": "MX 🇲🇽", "Wolverine Plushie": "CA 🇨🇦", "Nessie Plushie": "UK 🇬🇧",
    "Red Fox Plushie": "UK 🇬🇧", "Monkey Plushie": "AR 🇦🇷", "Chamois Plushie": "CH 🇨🇭",
    "Panda Plushie": "CN 🇨🇳", "Lion Plushie": "SA 🇿🇦", "Camel Plushie": "AE 🇦🇪",
    "Stingray Plushie": "KY 🇰🇾"
  };

  const ALL_ITEMS = { ...FLOWERS, ...PLUSHIES };

  // --- STYLES ---
  GM_addStyle(`
    #pointsExporter {
      position: fixed;
      top: 42px;
      left: 15px;
      z-index: 99999;
      background: #0b0b0b;
      color: #eee;
      font-size: 9px;
      font-family: monospace;
      border: 1px solid #444;
      border-radius: 6px;
      box-shadow: 0 4px 12px rgba(0,0,0,0.6);
      padding: 5px;
      width: 260px;
      max-height: 65vh;
      overflow-y: auto;
    }
    #pointsExporter h3 {
      margin: 0;
      cursor: pointer;
      font-size: 10px;
      background: #121212;
      padding: 4px;
      border-bottom: 1px solid #333;
    }
    #pointsExporterContent { display: none; padding-top: 4px; }
    .ctrls button {
      margin: 2px 2px;
      font-size: 9px;
      padding: 2px 5px;
      background: #171717;
      color: #eee;
      border: 1px solid #333;
      border-radius: 3px;
    }
    .ctrls button:hover { background: #232323; }
    .summary { margin-top: 4px; font-weight: 700; font-size: 10px; color: #bcdfff; }
    .low { color: #ff6060; margin-bottom: 6px; font-weight: bold; }
    .item-row { display:flex; justify-content:space-between; margin: 2px 0; }
    .item-name { flex: 1; }
    .item-loc { color:#bbb; font-size:8.5px; text-align:right; width:60px; }
  `);

  const panel = document.createElement('div');
  panel.id = 'pointsExporter';
  panel.innerHTML = `
    <h3>▶ 🌺 🐫 Points Exporter</h3>
    <div id="pointsExporterContent">
      <div class="ctrls">
        <button id="exp_refresh">Refresh</button>
        <button id="exp_setkey">Set API Key</button>
        <button id="exp_resetkey">Reset Key</button>
      </div>
      <div id="exp_status">Waiting for API key...</div>
      <div class="summary" id="exp_summary"></div>
      <div id="exp_list"></div>
    </div>
  `;
  document.body.appendChild(panel);

  const header = panel.querySelector('h3');
  const content = panel.querySelector('#pointsExporterContent');
  header.addEventListener('click', () => {
    const open = content.style.display === 'block';
    content.style.display = open ? 'none' : 'block';
    header.textContent = (open ? '▶' : '▼') + ' 🌺 🐫 Points Exporter';
  });

  const apiKeyStored = GM_getValue('tornAPIKey', null);
  let apiKey = apiKeyStored || null;
  const status = panel.querySelector('#exp_status');
  const summary = panel.querySelector('#exp_summary');
  const list = panel.querySelector('#exp_list');

  panel.querySelector('#exp_setkey').onclick = () => {
    const newKey = prompt('Enter your Torn API key (needs "items" permission):', apiKey || '');
    if (newKey) {
      apiKey = newKey.trim();
      GM_setValue('tornAPIKey', apiKey);
      status.textContent = 'API key saved.';
      loadData();
    }
  };
  panel.querySelector('#exp_resetkey').onclick = () => {
    GM_setValue('tornAPIKey', null);
    apiKey = null;
    status.textContent = 'API key cleared.';
  };
  panel.querySelector('#exp_refresh').onclick = () => loadData();

  // --- Fetch items via "items" (works during flights) ---
  async function loadData() {
    if (!apiKey) {
      status.textContent = 'No key. Please set API key.';
      return;
    }
    status.textContent = 'Fetching your items (works during flight)...';

    try {
      const res = await fetch(`https://api.torn.com/user/?selections=items&key=${apiKey}`);
      const data = await res.json();

      if (data.error) {
        status.textContent = `API error: ${data.error.error} (${data.error.code})`;
        return;
      }

      const itemList = data.items || {};
      const totals = {};

      // Sum counts for each name we care about
      for (const id in itemList) {
        const entry = itemList[id];
        const name = entry.name;
        if (ALL_ITEMS[name]) {
          totals[name] = (totals[name] || 0) + entry.quantity;
        }
      }

      renderUI(totals);
      status.textContent = 'Updated successfully ✅';
    } catch (err) {
      status.textContent = `Error fetching: ${err.message}`;
    }
  }

  // --- UI Rendering ---
  function renderUI(totals) {
    const flowerCounts = Object.entries(FLOWERS).map(([name, loc]) => ({
      name, loc, qty: totals[name] || 0
    }));
    const plushCounts = Object.entries(PLUSHIES).map(([name, loc]) => ({
      name, loc, qty: totals[name] || 0
    }));

    const flowerMin = Math.min(...flowerCounts.map(i => i.qty));
    const plushMin = Math.min(...plushCounts.map(i => i.qty));
    const sets = flowerMin + plushMin;
    const points = sets * 10;

    summary.textContent = `Sets: ${sets} | Points: ${points}`;

    const lowFlower = flowerCounts.reduce((a, b) => (a.qty < b.qty ? a : b));
    const lowPlush = plushCounts.reduce((a, b) => (a.qty < b.qty ? a : b));

    let html = '';
    html += `<div class="low">🌸 Low flower: ${lowFlower.name} (${lowFlower.qty}) — ${lowFlower.loc}</div>`;
    html += `<div class="low">🧸 Low plushie: ${lowPlush.name} (${lowPlush.qty}) — ${lowPlush.loc}</div>`;

    html += `<div><b>🌸 Flowers:</b></div>`;
    flowerCounts.forEach(i => html += `<div class="item-row"><span class="item-name">${i.name}</span><span>${i.qty}</span><span class="item-loc">${i.loc}</span></div>`);

    html += `<div style="margin-top:6px;"><b>🧸 Plushies:</b></div>`;
    plushCounts.forEach(i => html += `<div class="item-row"><span class="item-name">${i.name}</span><span>${i.qty}</span><span class="item-loc">${i.loc}</span></div>`);

    list.innerHTML = html;
  }

  // --- Init ---
  if (apiKey) loadData();
})();