Raw Source
brother-torn / Revivable + Last Action

// ==UserScript==
// @name         Revivable + Last Action
// @namespace    http://tampermonkey.net/
// @version      1.2.2
// @description  Shows if a user is revivable + last action on Torn loader (Use a public key)
// @author       Brother [2590792]
// @match        https://www.torn.com/page.php?sid=attack*
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_xmlhttpRequest
// @connect      api.torn.com
// @license      AGPL-3.0-only
// @downloadURL https://update.greasyfork.org/scripts/573635/Revivable%20%2B%20Last%20Action.user.js
// @updateURL https://update.greasyfork.org/scripts/573635/Revivable%20%2B%20Last%20Action.meta.js
// ==/UserScript==

(function () {
    'use strict';

    function getQueryParam(name, url) {
        const u = new URL(url || window.location.href);
        return u.searchParams.get(name);
    }

    async function gmGet(key, fallback) {
        if (typeof GM_getValue === 'function') return GM_getValue(key) ?? fallback;
        return localStorage.getItem(key) ?? fallback;
    }
    async function gmSet(key, value) {
        if (typeof GM_setValue === 'function') return GM_setValue(key, value);
        return localStorage.setItem(key, value);
    }

    function tornApiRequest(url) {
        return new Promise((resolve, reject) => {
            if (typeof GM_xmlhttpRequest === 'function') {
                GM_xmlhttpRequest({
                    method: 'GET',
                    url,
                    responseType: 'json',
                    onload: resp => resolve(resp.response || JSON.parse(resp.responseText)),
                    onerror: reject
                });
            } else {
                fetch(url).then(r => r.json()).then(resolve).catch(reject);
            }
        });
    }

    const user2ID = getQueryParam('user2ID');
    if (!user2ID) return;

    // Create label
    const label = document.createElement('div');
    label.innerHTML = '<div id="revivable">Loading...</div><div id="lastaction" style="font-size:13px;opacity:0.85">...</div>';
    label.style.position = 'fixed';
    label.style.zIndex = '999999';
    label.style.fontSize = '16px';
    label.style.fontWeight = 'bold';
    label.style.cursor = 'move';
    label.style.padding = '6px 10px';
    label.style.borderRadius = '6px';
    label.style.background = 'rgba(0,0,0,0.5)';
    label.style.color = '#fff';
    label.style.lineHeight = '1.3em';
    label.style.textAlign = 'center';   // <-- Center alignment here
    label.style.minWidth = '140px';     // helps centering look nicer

    document.body.appendChild(label);

    const elRevivable = label.querySelector('#revivable');
    const elLastAction = label.querySelector('#lastaction');

    // Load saved position
    (async () => {
        const pos = await gmGet('revive_label_pos', null);
        if (pos) {
            try {
                const { x, y } = JSON.parse(pos);
                label.style.left = x + 'px';
                label.style.top = y + 'px';
            } catch { }
        } else {
            label.style.left = '20px';
            label.style.top = '80px';
        }
    })();

    // Make draggable
    label.onmousedown = function (e) {
        e.preventDefault();
        let shiftX = e.clientX - label.getBoundingClientRect().left;
        let shiftY = e.clientY - label.getBoundingClientRect().top;

        function moveAt(pageX, pageY) {
            label.style.left = pageX - shiftX + 'px';
            label.style.top = pageY - shiftY + 'px';
        }

        function onMouseMove(e) {
            moveAt(e.pageX, e.pageY);
        }

        document.addEventListener('mousemove', onMouseMove);

        document.onmouseup = async function () {
            document.removeEventListener('mousemove', onMouseMove);
            document.onmouseup = null;
            await gmSet('revive_label_pos', JSON.stringify({
                x: parseInt(label.style.left),
                y: parseInt(label.style.top)
            }));
        };
    };
    label.ondragstart = () => false;

    // API key
    async function getApiKey() {
        let key = await gmGet('torn_api_key', null);
        if (!key) {
            key = prompt('Enter your Torn API key:');
            if (key) await gmSet('torn_api_key', key.trim());
        }
        return key;
    }

    async function updateStatus() {
        const apiKey = await getApiKey();
        if (!apiKey) {
            elRevivable.textContent = 'No API key';
            elRevivable.style.color = 'orange';
            elLastAction.textContent = '';
            return;
        }
        const url = `https://api.torn.com/user/${user2ID}?selections=profile&key=${apiKey}`;
        try {
            const data = await tornApiRequest(url);
            if (data.error) {
                elRevivable.textContent = 'API error';
                elRevivable.style.color = 'orange';
                elLastAction.textContent = '';
                return;
            }
            if (data.revivable === 1) {
                elRevivable.textContent = 'Revivable';
                elRevivable.style.color = 'lime';
            } else {
                elRevivable.textContent = 'Non-revivable';
                elRevivable.style.color = 'red';
            }

            if (data.last_action && data.last_action.relative) {
                elLastAction.textContent = 'Last action: ' + data.last_action.relative;
            } else {
                elLastAction.textContent = '';
            }
        } catch (err) {
            console.error(err);
            elRevivable.textContent = 'Request failed';
            elRevivable.style.color = 'orange';
            elLastAction.textContent = '';
        }
    }

    updateStatus();
})();