Virus Timer-PDA

Displays a movable timer for your virus

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey, Greasemonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да инсталирате разширение, като например Tampermonkey .

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Userscripts.

За да инсталирате скрипта, трябва да инсталирате разширение като Tampermonkey.

За да инсталирате този скрипт, трябва да имате инсталиран скриптов мениджър.

(Вече имам скриптов мениджър, искам да го инсталирам!)

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

(Вече имам инсталиран мениджър на стиловете, искам да го инсталирам!)

// ==UserScript==
// @name         Virus Timer-PDA
// @namespace    TornPDA
// @version      1.11
// @description  Displays a movable timer for your virus
// @author       Pholder [3623374]  
// @match        https://www.torn.com/*
// @grant        none
// @license      MIT
// @download     https://greasyfork.org/en/scripts/574536-virus-timer-pda
// ==/UserScript==



(function() {
    'use strict';

    const apiKey = "###PDA-APIKEY###";
    let virusUntil = 0;
    let isFetching = false;
    let lastKnownText = ""; // Memory for the last valid display

    const styleSheet = document.createElement("style");
    styleSheet.innerText = `
        #pda-virus-badge {
            position: fixed !important;
            z-index: 2147483647 !important;
            background: rgba(0, 0, 0, 0.95) !important;
            color: #228B22 !important;
            font-family: monospace !important;
            font-weight: bold !important;
            cursor: move !important;
            pointer-events: auto !important;
            user-select: none !important;
            touch-action: none !important;
            display: block !important;
            box-shadow: 0 0 10px rgba(0,0,0,0.7);
            border: 1px solid #228B22 !important;
        }
        @media screen and (min-width: 601px) { #pda-virus-badge { font-size: 13px !important; padding: 8px 12px !important; } }
        @media screen and (max-width: 600px) { #pda-virus-badge { font-size: 11px !important; padding: 3px 8px !important; border-radius: 3px !important; } }
    `;
    document.head.appendChild(styleSheet);

    function createBadge() {
        if (document.getElementById('pda-virus-badge')) return;
        const badge = document.createElement('div');
        badge.id = 'pda-virus-badge';
        badge.innerText = "Syncing...";
        const savedPos = localStorage.getItem('pda_virus_pos');
        if (savedPos) {
            const pos = JSON.parse(savedPos);
            badge.style.top = pos.top; badge.style.left = pos.left;
        } else {
            if (window.innerWidth <= 600) { badge.style.bottom = "80px"; badge.style.left = "10px"; }
            else { badge.style.top = "0px"; badge.style.left = "0px"; }
        }
        (document.body || document.documentElement).appendChild(badge);
        setupDragging(badge);
    }

    function setupDragging(el) {
        let active = false, currentX, currentY, initialX, initialY;
        const dragStart = (e) => {
            const clientX = e.type === "touchstart" ? e.touches[0].clientX : e.clientX;
            const clientY = e.type === "touchstart" ? e.touches[0].clientY : e.clientY;
            const rect = el.getBoundingClientRect();
            initialX = clientX - rect.left; initialY = clientY - rect.top;
            if (e.target === el) active = true;
        };
        const drag = (e) => {
            if (active) {
                e.preventDefault();
                const clientX = e.type === "touchmove" ? e.touches[0].clientX : e.clientX;
                const clientY = e.type === "touchmove" ? e.touches[0].clientY : e.clientY;
                el.style.left = (clientX - initialX) + "px"; el.style.top = (clientY - initialY) + "px";
                el.style.bottom = 'auto'; el.style.right = 'auto';
            }
        };
        const dragEnd = () => {
            if (active) {
                localStorage.setItem('pda_virus_pos', JSON.stringify({ top: el.style.top, left: el.style.left }));
                active = false;
            }
        };
        el.addEventListener("mousedown", dragStart);
        document.addEventListener("mousemove", drag);
        document.addEventListener("mouseup", dragEnd);
        el.addEventListener("touchstart", dragStart, {passive: false});
        document.addEventListener("touchmove", drag, {passive: false});
        document.addEventListener("touchend", dragEnd);
    }

    async function updateData() {
        if (apiKey.includes("PDA-APIKEY") || isFetching) return;
        isFetching = true;
        try {
            const res = await fetch(`https://api.torn.com/v2/user/virus?key=${apiKey}&st=${Date.now()}`);
            const data = await res.json();
            // Only update if we get a real timestamp back
            if (data.virus && data.virus.until > 0) {
                virusUntil = data.virus.until;
            }
        } catch (e) {}
        isFetching = false;
    }

    function getScrapedTime() {
        if (!window.location.href.includes('pc.php')) return null;
        const bodyText = document.body.innerText;
        const dayMatch = bodyText.match(/take (\d+) more day/i);
        if (dayMatch) return dayMatch[1] + "d+";
        const clockMatch = bodyText.match(/\d{1,2}:\d{2}:\d{2}/);
        return clockMatch ? clockMatch[0] : null;
    }

    function render() {
        const b = document.getElementById('pda-virus-badge');
        if (!b) { createBadge(); return; }

        const now = Math.floor(Date.now() / 1000);
        const isMobile = window.innerWidth <= 600;
        const icon = isMobile ? "" : "🦠 ";
        let newText = "";

        const scraped = getScrapedTime();

        // 1. Priority: If we have an active API timer
        if (virusUntil > now) {
            const s = virusUntil - now;
            const d = Math.floor(s / 86400);
            const h = Math.floor((s % 86400) / 3600);
            const m = Math.floor((s % 3600) / 60);
            const sec = s % 60;
            newText = d > 0 ? `${icon}${d}d ${h}h ${m}m` : `${icon}${h.toString().padStart(2, '0')}:${m.toString().padStart(2, '0')}:${sec.toString().padStart(2, '0')}`;
            b.style.opacity = "1";
        } 
        // 2. Secondary: Trust the page scraper if the API isn't ready
        else if (scraped) {
            newText = `${icon}~${scraped}`;
            b.style.opacity = "1";
        } 
        // 3. Fallback: Idle
        else {
            newText = isMobile ? "Idle" : "V: Idle";
            b.style.opacity = "0.5";
        }

        // --- THE FIX: Only update if the text changed and isn't flickering to empty ---
        if (newText !== lastKnownText) {
            b.innerText = newText;
            lastKnownText = newText;
        }
    }

    createBadge();
    updateData();
    setInterval(updateData, 45000); // Slower API pull to reduce collision
    setInterval(render, 1000);
    setInterval(createBadge, 3000);

    // When clicking "Begin", clear the cache to allow the scraper/API to refresh
    document.addEventListener('click', (e) => {
        if (window.location.href.includes('pc.php')) {
            virusUntil = 0; // Reset API memory so scraper takes over immediately
            setTimeout(updateData, 3000);
        }
    });
})();