您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Uses Torn public API (selection=display) to show flower and plushie set counts and per-item breakdown (total, av, ms). Prompt stores public API key locally.
当前为
// ==UserScript== // @name Torn Display Case Sets Tracker (API - fixed) // @namespace http://tampermonkey.net/ // @version 1.3 // @description Uses Torn public API (selection=display) to show flower and plushie set counts and per-item breakdown (total, av, ms). Prompt stores public API key locally. // @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" ]; // UI GM_addStyle(` #setTrackerPanel { position: fixed; top: 100px; left: 20px; width: 380px; background: #fff; color: #000; font-family: monospace; font-size: 12px; border: 1px solid #444; border-radius: 6px; padding: 8px; z-index: 2147483647; box-shadow: 0 0 10px rgba(0,0,0,0.35); max-height: 70vh; overflow-y: auto; line-height: 1.25; } #setTrackerPanel h4 { margin: 0 0 6px 0; font-size:13px; } #setTrackerPanel .controls { margin-bottom:6px; } #setTrackerPanel button { margin-right:6px; font-size:12px; padding:2px 6px; } #setTrackerPanel ul { margin: 4px 0 8px 14px; padding:0; } #setTrackerPanel li { margin: 2px 0; list-style: none; } #setTrackerPanel .item-name { display:inline-block; width:180px; } #setTrackerPanel .item-stats { display:inline-block; width:170px; text-align:right; } `); const panel = document.createElement('div'); panel.id = 'setTrackerPanel'; panel.innerHTML = ` <h4>Display Case Sets (API)</h4> <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> `; document.body.appendChild(panel); 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(); } // robust parser for API display data function aggregateDisplay(data) { const items = {}; // data.display may be array or object. also older tools use 'displaycase' or 'display' const displayRaw = data.display || data.displaycase || data.displayCase || null; if (!displayRaw) return items; // If object (map) convert to values const entries = Array.isArray(displayRaw) ? displayRaw : Object.values(displayRaw); for (const e of entries) { if (!e) continue; // determine name field const name = e.name || e.item_name || e.title || e.item || e.name_en || null; // determine quantity 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 if (typeof e.q !== 'undefined') qty = Number(e.q) || 0; else qty = 1; // many display entries are singletons if (!name) continue; items[name] = (items[name] || 0) + qty; } return items; } function calcSets(required, items) { // counts for each required item const counts = required.map(n => items[n] || 0); const complete = counts.length ? Math.min(...counts) : 0; const remaining = {}; const missing = {}; required.forEach((n) => { const total = items[n] || 0; const av = total - complete; // available after using complete full sets remaining[n] = av; missing[n] = av >= 1 ? 0 : (1 - av); // how many needed for next set }); return { complete, remaining, missing }; } function render(items) { const flowers = calcSets(FLOWERS, items); const plushies = calcSets(PLUSHIES, items); let html = ''; html += `<div><strong>Flowers sets:</strong> ${flowers.complete}</div>`; html += `<div><strong>Plushie sets:</strong> ${plushies.complete}</div>`; html += `<hr/>`; html += `<div><strong>Flowers breakdown</strong></div><ul>`; FLOWERS.forEach(name => { const total = items[name] || 0; const av = flowers.remaining[name]; const ms = flowers.missing[name]; html += `<li><span class="item-name">${name}</span><span class="item-stats">${total} (av ${av}, ms ${ms})</span></li>`; }); html += `</ul>`; html += `<div><strong>Plushies breakdown</strong></div><ul>`; PLUSHIES.forEach(name => { const total = items[name] || 0; const av = plushies.remaining[name]; const ms = plushies.missing[name]; html += `<li><span class="item-name">${name}</span><span class="item-stats">${total} (av ${av}, ms ${ms})</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 { // correct selection name: display 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; } // aggregate items const items = aggregateDisplay(data); // if nothing found, fallback: try 'displaycase' selection (some keys/version) if (Object.keys(items).length === 0) { // try alternative selection // note: some older examples use 'display'. we've already used it, but try 'displaycase' as fallback const altUrl = `https://api.torn.com/user/?selections=displaycase&key=${encodeURIComponent(apiKey)}`; const altRes = await fetch(altUrl); const altData = await altRes.json(); if (!altData.error) { const altItems = aggregateDisplay(altData); if (Object.keys(altItems).length) { render(altItems); statusEl.textContent = 'Loaded (fallback displaycase).'; return; } } } if (Object.keys(items).length === 0) { statusEl.textContent = 'No display items found in API response.'; contentEl.innerHTML = 'If you can see your display case in the site but API returns nothing, your key might lack permissions. Use a public/minimal key with display permission.'; return; } render(items); statusEl.textContent = 'Loaded from API.'; } catch (err) { statusEl.textContent = 'Fetch failed. Check network / key.'; contentEl.innerHTML = `<div style="color:#900;">${err.message}</div>`; } } // start if (!apiKey) askKey(false); else loadData(); })();