您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Tracks loot and stat drops from right-hand log in Cybroria, now with CSV export, drag, reset, and persistent storage features.
当前为
// ==UserScript== // @name Cybroria Loot Tracker (Enhanced) // @namespace http://tampermonkey.net/ // @version 3.0 // @description Tracks loot and stat drops from right-hand log in Cybroria, now with CSV export, drag, reset, and persistent storage features. // @author Skydragon // @match https://cybroria.com/* // @grant none // ==/UserScript== (function () { 'use strict'; const STORAGE_KEY = 'cybroria_loot_stats'; const POS_KEY = 'cybroria_tracker_position'; let timestamps = JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]'); let viewMode = 'hour'; // 'hour', 'day', or 'session' const trackedTypes = [ "Strength", "Agility", "Dexterity", "Vitality", "Energy Tap", "System Breach", "Chemsynthesis", "Cyber Harvest", "Credits", "Power Cells" ]; const trackerBox = document.createElement('div'); trackerBox.style.position = 'fixed'; trackerBox.style.top = localStorage.getItem(POS_KEY + '_top') || '10px'; trackerBox.style.left = localStorage.getItem(POS_KEY + '_left') || '10px'; trackerBox.style.background = '#000'; trackerBox.style.color = '#0f0'; trackerBox.style.padding = '10px'; trackerBox.style.fontSize = '13px'; trackerBox.style.fontFamily = 'monospace'; trackerBox.style.zIndex = 9999; trackerBox.style.cursor = 'move'; trackerBox.style.minWidth = '200px'; trackerBox.style.border = '1px solid #0f0'; trackerBox.innerHTML = '<strong>Cybroria Loot Tracker</strong><br>'; document.body.appendChild(trackerBox); makeDraggable(trackerBox); renderControls(); function makeDraggable(el) { let offsetX, offsetY, dragging = false; el.addEventListener('mousedown', (e) => { if (e.target.tagName === 'BUTTON' || e.target.tagName === 'SELECT') return; dragging = true; offsetX = e.clientX - el.offsetLeft; offsetY = e.clientY - el.offsetTop; }); document.addEventListener('mousemove', (e) => { if (!dragging) return; el.style.left = `${e.clientX - offsetX}px`; el.style.top = `${e.clientY - offsetY}px`; }); document.addEventListener('mouseup', () => { if (dragging) { localStorage.setItem(POS_KEY + '_top', el.style.top); localStorage.setItem(POS_KEY + '_left', el.style.left); } dragging = false; }); } function renderControls() { const controls = document.createElement('div'); controls.style.marginTop = '8px'; const resetBtn = document.createElement('button'); resetBtn.textContent = 'Reset'; resetBtn.onclick = () => { if (confirm("Reset all loot stats?")) { timestamps = []; localStorage.removeItem(STORAGE_KEY); updateBox(); } }; const exportBtn = document.createElement('button'); exportBtn.textContent = 'Export CSV'; exportBtn.style.marginLeft = '6px'; exportBtn.onclick = exportCSV; const modeSelect = document.createElement('select'); modeSelect.style.marginLeft = '6px'; ['hour', 'day', 'session'].forEach(mode => { const opt = document.createElement('option'); opt.value = mode; opt.textContent = `Per ${mode}`; modeSelect.appendChild(opt); }); modeSelect.value = viewMode; modeSelect.onchange = () => { viewMode = modeSelect.value; updateBox(); }; controls.appendChild(resetBtn); controls.appendChild(exportBtn); controls.appendChild(modeSelect); trackerBox.appendChild(controls); } function updateBox() { localStorage.setItem(STORAGE_KEY, JSON.stringify(timestamps)); const now = Date.now(); const hourAgo = now - 3600000; const dayAgo = now - 86400000; const stats = {}; timestamps.forEach(entry => { const show = viewMode === 'session' || (viewMode === 'hour' && entry.time >= hourAgo) || (viewMode === 'day' && entry.time >= dayAgo); if (!show) return; if (!stats[entry.item]) stats[entry.item] = 0; stats[entry.item] += entry.amount; }); let html = '<strong>Cybroria Loot Tracker</strong><br><br>'; html += `<u>Per ${viewMode.charAt(0).toUpperCase() + viewMode.slice(1)}:</u><br>`; for (const stat of trackedTypes) { if (stats[stat]) { html += `${stat}: ${stats[stat]}<br>`; } } trackerBox.innerHTML = html; renderControls(); } function exportCSV() { let csv = 'Item,Amount\n'; const totals = {}; timestamps.forEach(t => { if (!totals[t.item]) totals[t.item] = 0; totals[t.item] += t.amount; }); for (const item in totals) { csv += `${item},${totals[item]}\n`; } const blob = new Blob([csv], { type: 'text/csv' }); const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.download = 'cybroria_loot.csv'; link.click(); URL.revokeObjectURL(url); } function parseLootText(text) { text = text.replace(/\u00A0/g, ' ') .replace(/\s+/g, ' ') .replace(/\([\d,]+ for your syndicate\)/g, '') .trim(); const statValueMatch = text.match(/You have found ([\d,]+) ([A-Za-z ]+?) Stat Value/i); if (statValueMatch) { const amount = parseInt(statValueMatch[1].replace(/,/g, ''), 10); const statName = statValueMatch[2].trim(); if (trackedTypes.includes(statName)) { timestamps.push({ time: Date.now(), item: statName, amount }); updateBox(); } return; } const lootMatch = text.match(/You have found ([\d,]+) ([A-Za-z ]+)/); if (lootMatch) { const qty = parseInt(lootMatch[1].replace(/,/g, ''), 10); const item = lootMatch[2].trim(); if (trackedTypes.includes(item)) { timestamps.push({ time: Date.now(), item: item, amount: qty }); updateBox(); } } } function observeLootLog() { const seenLines = new Set(); setInterval(() => { const logSpans = document.querySelectorAll('app-loot-log span.ng-star-inserted'); logSpans.forEach(span => { const rawText = span.textContent.trim(); if (rawText.includes("You have found") && !seenLines.has(rawText)) { seenLines.add(rawText); parseLootText(rawText); } }); }, 1000); } window.addEventListener('load', () => { setTimeout(observeLootLog, 3000); updateBox(); }); })();