您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Track flowers and plushies in display case. Shows per-item totals and missing vs highest-owned. Uses Torn API (public key).
当前为
// ==UserScript== // @name Torn Display Case Tracker (Comparison Mode) // @namespace http://tampermonkey.net/ // @version 2.0 // @description Track flowers and plushies in display case. Shows per-item totals and missing vs highest-owned. Uses Torn API (public key). // @author Nova // @match https://www.torn.com/displaycase.php* // @grant GM_addStyle // @grant GM_getValue // @grant GM_setValue // ==/UserScript== (function() { 'use strict'; 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" ]; GM_addStyle(` #setTrackerPanel { position: fixed; top: 100px; left: 20px; width: 380px; background: #111; color: #eee; font-family: monospace; font-size: 12px; border: 1px solid #666; border-radius: 6px; z-index: 2147483647; box-shadow: 0 0 10px rgba(0,0,0,0.5); max-height: 70vh; overflow-y: auto; line-height: 1.3; } #setTrackerHeader { background: #222; padding: 6px; cursor: pointer; font-weight: bold; font-size: 13px; border-bottom: 1px solid #444; } #setTrackerContent { padding: 8px; display: none; } #setTrackerPanel button { margin: 4px 4px 8px 0; font-size: 12px; padding: 2px 6px; background: #333; color: #eee; border: 1px solid #555; border-radius: 4px; cursor: pointer; } #setTrackerPanel button:hover { background: #444; } #setTrackerPanel ul { margin: 4px 0 8px 14px; padding:0; } #setTrackerPanel li { margin: 2px 0; list-style: none; } .item-name { display:inline-block; width:180px; } .item-stats { display:inline-block; width:170px; text-align:right; } `); const panel = document.createElement('div'); panel.id = 'setTrackerPanel'; panel.innerHTML = ` <div id="setTrackerHeader">▶ Display Case Tracker</div> <div id="setTrackerContent"> <div class="controls"> <button id="tc_refresh">Refresh</button> <button id="tc_setkey">Set API Key</button> <button id="tc_resetkey">Reset Key</button> </div> <div id="tc_status">Waiting for key...</div> <div id="tc_content" style="margin-top:8px;"></div> </div> `; document.body.appendChild(panel); const headerEl = panel.querySelector('#setTrackerHeader'); const contentBox = panel.querySelector('#setTrackerContent'); headerEl.addEventListener('click', () => { const open = contentBox.style.display === 'block'; contentBox.style.display = open ? 'none' : 'block'; headerEl.textContent = (open ? '▶' : '▼') + ' Display Case Tracker'; }); const statusEl = panel.querySelector('#tc_status'); const contentEl = panel.querySelector('#tc_content'); panel.querySelector('#tc_refresh').addEventListener('click', () => loadData()); panel.querySelector('#tc_setkey').addEventListener('click', () => askKey(true)); panel.querySelector('#tc_resetkey').addEventListener('click', () => { GM_setValue('tornAPIKey', null); apiKey = null; statusEl.textContent = 'Key cleared. Click Set API Key.'; contentEl.innerHTML = ''; }); let apiKey = GM_getValue('tornAPIKey', null); async function askKey(force) { if (!apiKey || force) { const k = prompt('Enter your Torn PUBLIC API key (public/minimal access):', apiKey || ''); if (k) { apiKey = k.trim(); GM_setValue('tornAPIKey', apiKey); } } if (apiKey) loadData(); } function aggregateDisplay(data) { const items = {}; const displayRaw = data.display || data.displaycase || null; if (!displayRaw) return items; const entries = Array.isArray(displayRaw) ? displayRaw : Object.values(displayRaw); for (const e of entries) { if (!e) continue; const name = e.name || e.item_name || e.title || e.item || e.name_en || null; let qty = 0; if (typeof e.quantity !== 'undefined') qty = Number(e.quantity) || 0; else if (typeof e.qty !== 'undefined') qty = Number(e.qty) || 0; else if (typeof e.amount !== 'undefined') qty = Number(e.amount) || 0; else qty = 1; if (!name) continue; items[name] = (items[name] || 0) + qty; } return items; } function compareMode(required, items) { const counts = required.map(n => items[n] || 0); const highest = counts.length ? Math.max(...counts) : 0; const diff = {}; required.forEach(name => { const total = items[name] || 0; diff[name] = { total, missing: highest - total }; }); return { highest, diff }; } function render(items) { const flowers = compareMode(FLOWERS, items); const plushies = compareMode(PLUSHIES, items); let html = ''; html += `<div><strong>Flowers (highest: ${flowers.highest})</strong></div><ul>`; FLOWERS.forEach(name => { const d = flowers.diff[name]; html += `<li><span class="item-name">${name}</span><span class="item-stats">${d.total} (ms ${d.missing})</span></li>`; }); html += `</ul>`; html += `<div><strong>Plushies (highest: ${plushies.highest})</strong></div><ul>`; PLUSHIES.forEach(name => { const d = plushies.diff[name]; html += `<li><span class="item-name">${name}</span><span class="item-stats">${d.total} (ms ${d.missing})</span></li>`; }); html += `</ul>`; contentEl.innerHTML = html; } async function loadData() { contentEl.innerHTML = ''; if (!apiKey) { statusEl.textContent = 'No API key set. Click "Set API Key".'; return; } statusEl.textContent = 'Fetching display via API...'; try { const url = `https://api.torn.com/user/?selections=display&key=${encodeURIComponent(apiKey)}`; const res = await fetch(url); const data = await res.json(); if (data.error) { statusEl.textContent = `API error: ${data.error.error} (code ${data.error.code})`; contentEl.innerHTML = ''; return; } const items = aggregateDisplay(data); if (Object.keys(items).length === 0) { statusEl.textContent = 'No display items found.'; return; } render(items); statusEl.textContent = 'Loaded.'; } catch (err) { statusEl.textContent = 'Fetch failed.'; contentEl.innerHTML = `<div style="color:#f88;">${err.message}</div>`; } } if (!apiKey) askKey(false); else loadData(); })();