您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Compact tracker for flowers and plushies: short name | total(after sets) | (ms X) | CODE + flag. Auto-calculates full sets and points (10 pts/set). Uses Torn public API key.
当前为
// ==UserScript== // @name 🌺 🐫 Points Exporter // @namespace http://tampermonkey.net/ // @version 2.8 // @description Compact tracker for flowers and plushies: short name | total(after sets) | (ms X) | CODE + flag. Auto-calculates full sets and points (10 pts/set). Uses Torn public API 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": "MX 🇲🇽", "Orchid": "US 🇺🇸", "Violet": "SA 🇿🇦", "Cherry": "JP 🇯🇵", "Peony": "CN 🇨🇳", "Ceibo": "AR 🇦🇷", "Edelweiss": "CH 🇨🇭", "Crocus": "CA 🇨🇦", "Heather": "UK 🇬🇧", "Tribulus": "AE 🇦🇪", "Banana": "KY 🇰🇾" }; const PLUSHIES = { "Sheep": "B.B 🏪", "Teddy": "B.B 🏪", "Kitten": "B.B 🏪", "Jaguar": "MX 🇲🇽", "Wolverine": "US 🇺🇸", "Nessie": "UK 🇬🇧", "Fox": "UK 🇬🇧", "Monkey": "AR 🇦🇷", "Chamois": "CH 🇨🇭", "Panda": "CN 🇨🇳", "Lion": "SA 🇿🇦", "Camel": "AE 🇦🇪", "Stingray": "KY 🇰🇾" }; GM_addStyle(` #setTrackerPanel { position: fixed; top: 100px; left: 18px; width: 240px; background: #0b0b0b; color: #eaeaea; font-family: "DejaVu Sans Mono", "Liberation Mono", monospace; font-size: 9px; border: 1px solid #444; border-radius: 6px; z-index: 2147483647; box-shadow: 0 6px 16px rgba(0,0,0,0.5); max-height: 65vh; overflow-y: auto; line-height: 1.1; } #setTrackerHeader { background: #121212; padding: 4px 6px; cursor: pointer; font-weight: 700; font-size: 10px; border-bottom: 1px solid #333; user-select: none; } #setTrackerContent { padding: 5px; display: none; } #setTrackerPanel .controls { margin-bottom:5px; } #setTrackerPanel button { margin: 2px 2px 5px 0; font-size: 9px; padding: 2px 5px; background: #171717; color: #eaeaea; border: 1px solid #333; border-radius: 3px; cursor: pointer; } #setTrackerPanel button:hover { background: #222; } .summary-line { font-weight:700; margin-bottom:6px; font-size:10px; color:#dfe7ff; } .group-title { font-weight:700; margin-top:4px; margin-bottom:3px; font-size:9.5px; } ul.item-list { margin:0 0 4px 0; padding:0; list-style:none; } li.item-row { display:flex; align-items:center; gap:4px; padding:1px 0; white-space:nowrap; } .item-name { flex:1 1 auto; min-width:0; overflow:hidden; text-overflow:ellipsis; } .item-total { flex:0 0 40px; text-align:right; color:#cfe8c6; } .item-ms { flex:0 0 42px; text-align:right; color:#f7b3b3; } .item-loc { flex:0 0 56px; text-align:right; color:#bcbcbc; font-size:8.5px; } #tc_status { font-size:9px; color:#bdbdbd; margin-bottom:5px; } `); const panel = document.createElement('div'); panel.id = 'setTrackerPanel'; panel.innerHTML = ` <div id="setTrackerHeader">▶ 🌺 🐫 Points Exporter</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_summary"></div> <div id="tc_content"></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 ? '▶' : '▼') + ' 🌺 🐫 Points Exporter'; }); const statusEl = panel.querySelector('#tc_status'); const summaryEl = panel.querySelector('#tc_summary'); 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 = ''; summaryEl.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 || 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 || null; if (!name) continue; const qty = Number(e.quantity ?? e.qty ?? e.amount ?? 1) || 0; items[name] = (items[name] || 0) + qty; } return items; } function calcSetsAndRemainder(requiredMap, items) { const names = Object.keys(requiredMap); const counts = names.map(n => items[n] || 0); const sets = counts.length ? Math.min(...counts) : 0; const remainder = {}; names.forEach((n, i) => { remainder[n] = Math.max(0, (items[n] || 0) - sets); }); return { names, sets, remainder }; } function render(items) { const f = calcSetsAndRemainder(FLOWERS, items); const p = calcSetsAndRemainder(PLUSHIES, items); const totalSets = f.sets + p.sets; const totalPoints = totalSets * 10; summaryEl.innerHTML = `<div class="summary-line">Total sets: ${totalSets} | Points: ${totalPoints}</div>`; let html = ''; html += `<div class="group-title">Flowers — sets: ${f.sets} | pts: ${f.sets * 10}</div>`; html += `<ul class="item-list">`; f.names.forEach(name => { const rem = f.remainder[name]; html += `<li class="item-row"> <span class="item-name">${name}</span> <span class="item-total">${rem}</span> <span class="item-ms"></span> <span class="item-loc">${FLOWERS[name]}</span> </li>`; }); html += `</ul>`; html += `<div class="group-title">Plushies — sets: ${p.sets} | pts: ${p.sets * 10}</div>`; html += `<ul class="item-list">`; p.names.forEach(name => { const rem = p.remainder[name]; html += `<li class="item-row"> <span class="item-name">${name}</span> <span class="item-total">${rem}</span> <span class="item-ms"></span> <span class="item-loc">${PLUSHIES[name]}</span> </li>`; }); html += `</ul>`; contentEl.innerHTML = html; } async function loadData() { contentEl.innerHTML = ''; summaryEl.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. Key may lack permission.'; 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(); })();