Torn Display Case Sets Tracker (API Version)

Track flower and plushie sets in display case using Torn API (public key required)

当前为 2025-10-01 提交的版本,查看 最新版本

// ==UserScript==
// @name         Torn Display Case Sets Tracker (API Version)
// @namespace    http://tampermonkey.net/
// @version      1.2
// @description  Track flower and plushie sets in display case using Torn API (public key required)
// @author       Nova
// @match        https://www.torn.com/displaycase.php*
// @grant        GM_addStyle
// @grant        GM_getValue
// @grant        GM_setValue
// ==/UserScript==

(function() {
    'use strict';

    // Required sets
    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"
    ];

    // Style
    GM_addStyle(`
      #setTrackerPanel {
        position: fixed;
        top: 100px;
        left: 20px;
        width: 350px;
        background: #fff;
        color: #000;
        font-family: monospace;
        font-size: 12px;
        border: 1px solid #444;
        border-radius: 6px;
        padding: 8px;
        z-index: 9999;
        box-shadow: 0 0 10px rgba(0,0,0,0.4);
        max-height: 500px;
        overflow-y: auto;
      }
      #setTrackerPanel b { font-size: 13px; }
      #setTrackerPanel ul { margin: 2px 0 6px 15px; padding: 0; }
      #setTrackerPanel li { margin: 0; list-style: none; }
    `);

    const panel = document.createElement("div");
    panel.id = "setTrackerPanel";
    panel.innerHTML = `<b>Loading display case...</b>`;
    document.body.appendChild(panel);

    let apiKey = GM_getValue("tornAPIKey", null);

    async function askKey() {
        let key = prompt("Enter your Torn Public API Key:", apiKey || "");
        if (key) {
            GM_setValue("tornAPIKey", key);
            apiKey = key;
            loadData();
        }
    }

    async function loadData() {
        if (!apiKey) {
            panel.innerHTML = "No API key set.<br><button id='setKeyBtn'>Set API Key</button>";
            document.getElementById("setKeyBtn").addEventListener("click", askKey);
            return;
        }

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

            if (data.error) {
                panel.innerHTML = `Error: ${data.error.error}<br><button id='resetKeyBtn'>Reset Key</button>`;
                document.getElementById("resetKeyBtn").addEventListener("click", () => {
                    GM_setValue("tornAPIKey", null);
                    apiKey = null;
                    askKey();
                });
                return;
            }

            const items = {};
            for (const id in data.displaycase) {
                const obj = data.displaycase[id];
                items[obj.name] = (items[obj.name] || 0) + obj.quantity;
            }

            updatePanel(items);

        } catch (e) {
            panel.innerHTML = "Failed to fetch display case.";
        }
    }

    function calcSets(required, items) {
        const counts = required.map(name => items[name] || 0);
        const complete = Math.min(...counts);

        const remaining = {};
        const missing = {};
        required.forEach(name => {
            remaining[name] = (items[name] || 0) - complete;
            missing[name] = remaining[name] >= 1 ? 0 : 1 - remaining[name];
        });

        return { complete, remaining, missing };
    }

    function updatePanel(items) {
        const flowers = calcSets(FLOWERS, items);
        const plushies = calcSets(PLUSHIES, items);

        let html = `
          <b>Flower Sets:</b> ${flowers.complete}<br>
          <b>Plushie Sets:</b> ${plushies.complete}<br><br>
          <b>Flowers Breakdown:</b><br>
          <ul>
            ${FLOWERS.map(name => {
                const total = items[name] || 0;
                const av = flowers.remaining[name];
                const ms = flowers.missing[name];
                return `<li>${name}: ${total} (av ${av}, ms ${ms})</li>`;
            }).join("")}
          </ul>
          <b>Plushies Breakdown:</b><br>
          <ul>
            ${PLUSHIES.map(name => {
                const total = items[name] || 0;
                const av = plushies.remaining[name];
                const ms = plushies.missing[name];
                return `<li>${name}: ${total} (av ${av}, ms ${ms})</li>`;
            }).join("")}
          </ul>
        `;

        panel.innerHTML = html;
    }

    // Start
    if (!apiKey) {
        askKey();
    } else {
        loadData();
    }
})();