Virus Timer-PDA

Displays a movable timer for your virus

Vous devrez installer une extension telle que Tampermonkey, Greasemonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Userscripts pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension de gestionnaire de script utilisateur pour installer ce script.

(J'ai déjà un gestionnaire de scripts utilisateur, laissez-moi l'installer !)

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

(J'ai déjà un gestionnaire de style utilisateur, laissez-moi l'installer!)

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