Virus Timer-PDA

Displays a movable timer for your virus

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

You will need to install an extension such as Tampermonkey to install this script.

Tendrás que instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Tendrás que instalar una extensión como Tampermonkey antes de poder instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

// ==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);
        }
    });
})();