Torn - Hospital Timer (Native UI)

Replaces the "IN HOSPITAL" message on the attack page with a live countdown timer

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

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

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==UserScript==
// @name         Torn - Hospital Timer (Native UI)
// @namespace    http://tampermonkey.net/
// @version      2.0
// @description  Replaces the "IN HOSPITAL" message on the attack page with a live countdown timer
// @license      MIT
// @author       JayMike
// @match        https://www.torn.com/loader.php?sid=attack*
// @match        https://www.torn.com/loader2.php?sid=attack*
// @grant        GM_xmlhttpRequest
// @grant        GM_getValue
// @grant        GM_setValue
// @connect      api.torn.com
// ==/UserScript==

(function () {
    'use strict';

    // ─── CONFIG ───────────────────────────────────────────────────────────────
    const API_KEY = GM_getValue('torn_api_key', '');
    const REFRESH_EVERY = 15; // seconds between API re-sync polls
    // ──────────────────────────────────────────────────────────────────────────

    if (!API_KEY) {
        promptForApiKey();
        return;
    }

    const params = new URLSearchParams(window.location.search);
    const targetId = params.get('user2ID');
    if (!targetId) return;

    let countdownInterval = null;
    let pollInterval = null;
    let injected = false;

    // ─── FIND THE HOSPITAL MESSAGE BOX ───────────────────────────────────────
    function findHospitalBox() {
        const allDivs = document.querySelectorAll('div');
        for (const div of allDivs) {
            if (
                div.innerText &&
                div.innerText.includes('CURRENTLY IN HOSPITAL') &&
                div.children.length === 0
            ) {
                return div;
            }
        }
        return null;
    }

    function waitForHospitalBox(callback, timeout = 15000) {
        const start = Date.now();
        const check = setInterval(() => {
            const box = findHospitalBox();
            if (box) {
                clearInterval(check);
                callback(box);
            } else if (Date.now() - start > timeout) {
                clearInterval(check);
                callback(null);
            }
        }, 300);
    }

    // ─── INJECT LIVE TIMER INTO THE HOSPITAL BOX ─────────────────────────────
    function injectTimer(box, untilTimestamp) {
        box.innerHTML = '';
        box.style.display = 'flex';
        box.style.flexDirection = 'column';
        box.style.alignItems = 'center';
        box.style.justifyContent = 'center';
        box.style.gap = '4px';

        const topText = document.createElement('div');
        topText.textContent = 'THIS PERSON IS CURRENTLY IN HOSPITAL';
        topText.style.cssText = 'color:#e8581c;font-weight:bold;font-size:13px;text-align:center;line-height:1.3;';

        const timerEl = document.createElement('div');
        timerEl.id = 'hosp-live-countdown';
        timerEl.style.cssText = 'color:#ffffff;font-size:26px;font-weight:bold;font-family:monospace;letter-spacing:3px;margin-top:6px;text-shadow:0 0 8px rgba(232,88,28,0.8);';

        const labelEl = document.createElement('div');
        labelEl.id = 'hosp-live-label';
        labelEl.textContent = 'until out of hospital';
        labelEl.style.cssText = 'color:#aaa;font-size:10px;';

        box.appendChild(topText);
        box.appendChild(timerEl);
        box.appendChild(labelEl);

        injected = true;
        startCountdown(untilTimestamp);
    }

    function startCountdown(untilTimestamp) {
        clearInterval(countdownInterval);

        function tick() {
            const el = document.getElementById('hosp-live-countdown');
            const labelEl = document.getElementById('hosp-live-label');
            if (!el) return;

            const now = Math.floor(Date.now() / 1000);
            const diff = untilTimestamp - now;

            if (diff <= 0) {
                el.textContent = 'OUT NOW!';
                el.style.color = '#2ecc71';
                el.style.textShadow = '0 0 8px rgba(46,204,113,0.8)';
                if (labelEl) labelEl.textContent = 'ready to attack';
                clearInterval(countdownInterval);
                setTimeout(fetchAndUpdate, 2000);
                return;
            }

            const h = Math.floor(diff / 3600);
            const m = Math.floor((diff % 3600) / 60);
            const s = diff % 60;
            el.textContent = `${pad(h)}:${pad(m)}:${pad(s)}`;
        }

        tick();
        countdownInterval = setInterval(tick, 1000);
    }

    // ─── API FETCH ─────────────────────────────────────────────────────────────
    function fetchAndUpdate() {
        GM_xmlhttpRequest({
            method: 'GET',
            url: `https://api.torn.com/user/${targetId}?selections=profile&key=${API_KEY}&comment=HospTimerNative`,
            onload: (res) => {
                try {
                    const data = JSON.parse(res.responseText);
                    if (data.error) return;

                    const status = data.status || {};
                    const until = status.until || 0;

                    if (status.state === 'Hospital') {
                        if (!injected) {
                            const box = findHospitalBox();
                            if (box) injectTimer(box, until);
                        } else {
                            // Re-sync countdown with fresh API timestamp
                            startCountdown(until);
                        }
                    }
                } catch (e) {}
            },
        });
    }

    // ─── MAIN ──────────────────────────────────────────────────────────────────
    waitForHospitalBox((box) => {
        if (!box) return; // Target not in hospital

        GM_xmlhttpRequest({
            method: 'GET',
            url: `https://api.torn.com/user/${targetId}?selections=profile&key=${API_KEY}&comment=HospTimerNative`,
            onload: (res) => {
                try {
                    const data = JSON.parse(res.responseText);
                    if (data.error) return;
                    const until = (data.status && data.status.until) || 0;
                    injectTimer(box, until);
                    pollInterval = setInterval(fetchAndUpdate, REFRESH_EVERY * 1000);
                } catch (e) {}
            },
        });
    });

    // ─── HELPERS ───────────────────────────────────────────────────────────────
    function pad(n) {
        return String(n).padStart(2, '0');
    }

    function promptForApiKey() {
        const key = window.prompt(
            'Torn Hospital Timer\n\nEnter your Torn API key (read-only "user" access).\nYou only need to do this once.',
            ''
        );
        if (key && key.trim().length > 5) {
            GM_setValue('torn_api_key', key.trim());
            window.location.reload();
        }
    }

    window.addEventListener('beforeunload', () => {
        clearInterval(countdownInterval);
        clearInterval(pollInterval);
    });

})();