Torn Cache Log Exporter

Single-jump scroll + export all caches including Armor; quick-jump button on main log page

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, Greasemonkey alebo Violentmonkey.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie, ako napríklad Tampermonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, % alebo Violentmonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey alebo Userscripts.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie, ako napríklad Tampermonkey.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie správcu používateľských skriptov.

(Už mám správcu používateľských skriptov, nechajte ma ho nainštalovať!)

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

(Už mám správcu používateľských štýlov, nechajte ma ho nainštalovať!)

// ==UserScript==
// @name         Torn Cache Log Exporter
// @namespace    torn.cache.export
// @version      9.4
// @description  Single-jump scroll + export all caches including Armor; quick-jump button on main log page
// @match        https://www.torn.com/page.php?sid=log*
// @grant        none
// ==/UserScript==

(function () {
    'use strict';

    const ANALYZER_URL = "https://torn-two.vercel.app/";
    const params = new URLSearchParams(window.location.search);

    // ── On any log page that is NOT the cache log, show a quick-jump button ──
    function addNavButton() {
        if (document.getElementById("cacheNavBtn")) return;
        const btn = document.createElement("button");
        btn.id = "cacheNavBtn";
        btn.innerText = "\uD83D\uDCE6 Go to Cache Logs";
        btn.style.position = "fixed";
        btn.style.top = "100px";
        btn.style.right = "20px";
        btn.style.zIndex = "9999";
        btn.style.padding = "10px 16px";
        btn.style.background = "#7c3aed";
        btn.style.color = "white";
        btn.style.border = "none";
        btn.style.borderRadius = "8px";
        btn.style.cursor = "pointer";
        btn.style.fontWeight = "bold";
        btn.style.fontSize = "13px";
        btn.style.boxShadow = "0 2px 8px rgba(0,0,0,0.35)";
        btn.style.transition = "background 0.15s";
        btn.onmouseenter = () => { btn.style.background = "#6d28d9"; };
        btn.onmouseleave = () => { btn.style.background = "#7c3aed"; };
        btn.onclick = () => { window.location.href = "https://www.torn.com/page.php?sid=log&log=2615"; };
        document.body.appendChild(btn);
    }

    function safeInit(fn) {
        if (document.readyState === "complete") {
            setTimeout(fn, 1000);
        } else {
            window.addEventListener("load", () => setTimeout(fn, 1000));
        }
    }

    if (params.get("log") !== "2615") {
        safeInit(addNavButton);
        return;
    }

    // ── Cache log page (log=2615) ─────────────────────────────────────────────

    function showNotification(message) {
        const existing = document.getElementById("tornExporterToast");
        if (existing) existing.remove();

        const toast = document.createElement("div");
        toast.id = "tornExporterToast";
        toast.innerText = message;
        Object.assign(toast.style, {
            position:     "fixed",
            bottom:       "30px",
            left:         "50%",
            transform:    "translateX(-50%)",
            background:   "#1e293b",
            color:        "white",
            padding:      "12px 20px",
            borderRadius: "8px",
            zIndex:       "99999",
            fontWeight:   "bold",
            fontSize:     "14px",
            boxShadow:    "0 2px 10px rgba(0,0,0,0.4)",
            opacity:      "1",
            transition:   "opacity 0.5s ease",
        });

        document.body.appendChild(toast);

        setTimeout(() => {
            toast.style.opacity = "0";
            setTimeout(() => toast.remove(), 500);
        }, 4000);
    }

    function addUI() {
        if (document.getElementById("cacheExportContainer")) return;
        const container = document.createElement("div");
        container.id = "cacheExportContainer";
        container.style.position = "fixed";
        container.style.top = "100px";
        container.style.right = "20px";
        container.style.zIndex = "9999";
        container.style.display = "flex";
        container.style.flexDirection = "column";
        container.style.gap = "8px";

        // ── One click = one jump. No loop, no automation. ─────────────────────
        const jumpBtn  = createButton("⬇️ Jump to Bottom", "#2563eb", jumpToBottom);
        const exportBtn = createButton("💾 Export Logs",   "#16a34a", exportLogs);

        container.appendChild(jumpBtn);
        container.appendChild(exportBtn);
        document.body.appendChild(container);
    }

    function createButton(text, color, onClick) {
        const btn = document.createElement("button");
        btn.innerText = text;
        btn.style.padding = "8px 12px";
        btn.style.background = color;
        btn.style.color = "white";
        btn.style.border = "none";
        btn.style.borderRadius = "6px";
        btn.style.cursor = "pointer";
        btn.style.fontWeight = "bold";
        btn.onclick = onClick;
        return btn;
    }

    // ── Single scroll to bottom — equivalent to pressing the End key ──────────
    function jumpToBottom() {
        window.scrollTo({ top: document.body.scrollHeight, behavior: "smooth" });
        showNotification("⬇️ Jumped to bottom. Scroll back up manually to load more entries, then export.");
    }

    function parseCacheType(text) {
        if (text.includes("Armor Cache"))       return "Armor";
        if (text.includes("Melee Cache"))       return "Melee";
        if (text.includes("Small Arms Cache"))  return "Small Arms";
        if (text.includes("Medium Arms Cache")) return "Medium Arms";
        if (text.includes("Heavy Arms Cache"))  return "Heavy Arms";
        return null;
    }

    function detectRarity(el) {
        const cls = el.className.toLowerCase();
        if (cls.includes("text-red"))    return "Red";
        if (cls.includes("text-orange")) return "Orange";
        if (cls.includes("text-yellow")) return "Yellow";
        return "Unknown";
    }

    function extractTimestamp(entry) {
        const parent = entry.closest("li, tr, div");
        if (!parent) return null;
        const match = parent.innerText.match(/\d{2}:\d{2}:\d{2}\s-\s\d{2}\/\d{2}\/\d{2}/);
        return match ? match[0] : null;
    }

    function exportLogs() {
        const logEntries = document.querySelectorAll("span.log-text");
        const results = [];
        logEntries.forEach(entry => {
            const text = entry.innerText;
            if (!text.includes("Cache") || !text.includes("gained")) return;
            const cacheType = parseCacheType(text);
            if (!cacheType) return;
            const colored = entry.querySelector(".text-yellow, .text-orange, .text-red");
            if (!colored) return;
            const rarity = detectRarity(colored);
            const timestamp = extractTimestamp(entry);
            const fullText = colored.innerText.trim();
            const bonusMatch = fullText.match(/\((.*?)\)/);
            const bonus = bonusMatch ? bonusMatch[1] : null;
            const itemName = bonusMatch ? fullText.replace(/\(.*?\)/, "").trim() : fullText;
            results.push({
                timestamp,
                cacheType,
                weaponName: itemName,
                bonus,
                doubleBonus: cacheType === "Armor" ? false : (bonus && bonus.includes("&")) || false,
                rarity
            });
        });
        downloadJSON(results);
    }

    function downloadJSON(data) {
        const blob = new Blob([JSON.stringify(data, null, 2)], { type: "application/json" });
        const url = URL.createObjectURL(blob);
        const a = document.createElement("a");
        a.href = url;
        a.download = "torn_cache_logs_full.json";
        a.click();
        URL.revokeObjectURL(url);
        showNotification(`💾 Exported ${data.length} entries! Opening analyzer...`);
        setTimeout(() => window.open(ANALYZER_URL, "_blank"), 1200);
    }

    safeInit(addUI);
})();