Show NPC Time Til Next Level at Loader

Shows the time til the next level for NPCs at the attack loader page. Uses https://api.lzpt.io/loot to get the data. Also modifies tab title, disable by modifying IF_UPDATE_TAB_TITLE to false.

目前为 2024-12-05 提交的版本。查看 最新版本

// ==UserScript==
// @name         Show NPC Time Til Next Level at Loader
// @namespace    http://tampermonkey.net/
// @version      0.4
// @description  Shows the time til the next level for NPCs at the attack loader page. Uses https://api.lzpt.io/loot to get the data.  Also modifies tab title, disable by modifying IF_UPDATE_TAB_TITLE to false. 
// @author       Hesper [2924630]
// @include      https://www.torn.com/loader.php?sid=attack&user2ID=*
// @grant        GM.xmlHttpRequest
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // Displays time remaining until the next level in the tab title.
    // Set to false to disable
    const IF_UPDATE_TAB_TITLE = true;

    // Can optionally insert a public API key to help with the seconds offset in the time displayed due to the difference in the time between the server and your device.
    const PUBLIC_API_KEY = '';

    const validIDs = [10, 20, 21, 4, 19, 15, 17];

    // Function to fetch JSON data
    function fetchData(url) {
        return new Promise((resolve, reject) => {
            GM.xmlHttpRequest({
                method: 'GET',
                url: url,
                onload: function(response) {
                    if (response.status >= 200 && response.status < 300) {
                        try {
                            const data = JSON.parse(response.responseText);
                            resolve(data);
                        } catch (e) {
                            reject(e);
                        }
                    } else {
                        reject(new Error(`HTTP error! status: ${response.status}`));
                    }
                },
                onerror: function(err) {
                    reject(err);
                }
            });
        });
    }

    function calculateTimeRemaining(currentTime, hospOutTime) {
        const levelTimes = [0, 30, 90, 210, 450]; // Minutes after hosp_out for each level
        let nextLevel = 1;
        let timeRemaining = 0;

        if (currentTime < hospOutTime) {
            timeRemaining = hospOutTime - currentTime;
        } else {
            for (let level = 2; level <= 5; level++) {
                const targetTime = hospOutTime + levelTimes[level - 1] * 60; // Convert minutes to seconds
                timeRemaining = targetTime - currentTime;
                // If the time remaining is positive, the current level is the previous level
                if (timeRemaining > 0) {
                    nextLevel = level;
                    break;
                }
            }
        }

        return { nextLevel, timeRemaining: timeRemaining > 0 ? timeRemaining : 0 };
    }

    function formatTime(seconds) {
        const minutes = Math.floor(seconds / 60);
        const hours = Math.floor(minutes / 60);
        const remainingMinutes = minutes % 60;
        const remainingSeconds = seconds % 60;
        if (hours === 0) {
            if (remainingMinutes === 0) {
                return `${remainingSeconds}s`;
            }
            return `${remainingMinutes}m ${remainingSeconds}s`;
        }
        return `${hours}h ${remainingMinutes}m ${remainingSeconds}s`;
    }

    function updateDiv(npc, currentTime) {

        const hospOutTime = npc.hosp_out;
        const { nextLevel, timeRemaining } = calculateTimeRemaining(currentTime, hospOutTime);
        let level = nextLevel - 1;

        const targetDiv = document.querySelector('.titleContainer___QrlWP');
        if (targetDiv) {
            let newDiv = document.querySelector('#npc-level-info');
            if (!newDiv) {
                newDiv = document.createElement('div');
                newDiv.id = 'npc-level-info';
                newDiv.style.marginTop = '15px';
                newDiv.style.marginRight = '10px';
                newDiv.style.textAlign = 'right';
                newDiv.style.fontSize = '1em';
                newDiv.style.lineHeight = '1.2em';

                targetDiv.parentNode.insertBefore(newDiv, targetDiv.nextSibling);
            }
            if (level < 5) {
                newDiv.innerHTML = `Currently <b>Level ${level}</b><br />Time til Level ${level + 1}: <b>${formatTime(timeRemaining)}</b>`;
                if (IF_UPDATE_TAB_TITLE) document.title = `${formatTime(timeRemaining)} til Level ${level + 1} - ${npc.name}`;
            } else {
                newDiv.innerHTML = `Currently <b>Level ${level}</b>`;
                if (IF_UPDATE_TAB_TITLE) document.title = `Level ${level} -  ${npc.name}`;
            }
        }
    }

    // async function startUpdating(user2ID) {
    //     // Check if the user2ID is valid
    //     const npcData = await fetchData();
    //     const npc = npcData.npcs[user2ID];
    //     if (!npc) return;

    //     // Start updating the timer every second
    //     setInterval(() => {
    //         const currentTime = Math.floor(Date.now() / 1000);
    //         updateDiv(npc, currentTime);
    //     }, 1000);
    // }

    async function startUpdating() {
        try {
            let timeOffset = 0;
            if (!PUBLIC_API_KEY !== '') {
                // Fetch the timestamp from Torn API
                const tornData = await fetchData(`https://api.torn.com/torn/?selections=timestamp&key=${PUBLIC_API_KEY}`);
                const tornTimestamp = tornData.timestamp;
                // console.log(`[NPCTimerAtLoader]: Torn timestamp: ${tornTimestamp}`);
                const localTimestamp = Math.floor(Date.now() / 1000);
                // console.log(`[NPCTimerAtLoader]: Local timestamp: ${localTimestamp}`);
                timeOffset = localTimestamp - tornTimestamp;
                console.log(`[NPCTimerAtLoader]: Time offset: ${timeOffset}`);
            }
    
            // Fetch the NPC data
            const npcData = await fetchData('https://api.lzpt.io/loot');
            setInterval(() => {
                const currentTime = Math.floor(Date.now() / 1000) - timeOffset;
                updateDiv(npcData, currentTime);
            }, 1000);
        } catch (error) {
            console.error('Failed to fetch NPC data:', error);
        }
    }

    window.addEventListener('load', () => {
        // check if the user2ID is valid
        const user2ID = parseInt(new URLSearchParams(window.location.search).get('user2ID'), 10);
        if (validIDs.includes(user2ID)) {
            startUpdating(user2ID);
        }
    });
})();