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 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

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

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

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