Virus Timer-PDA

Displays a movable timer for your virus

Você precisará instalar uma extensão como Tampermonkey, Greasemonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Userscripts para instalar este script.

Você precisará instalar uma extensão como o Tampermonkey para instalar este script.

Você precisará instalar um gerenciador de scripts de usuário para instalar este script.

(Eu já tenho um gerenciador de scripts de usuário, me deixe instalá-lo!)

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

(Eu já possuo um gerenciador de estilos de usuário, me deixar fazer a instalação!)

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