Torn Workstats Viewer

  Show total working stats on profile and company pages

当前为 2025-09-06 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name          Torn Workstats Viewer
// @namespace     http://tampermonkey.net/
// @version       50.0
// @description   Show total working stats on profile and company pages
// @match         https://www.torn.com/profiles.php*
// @match         https://www.torn.com/joblist.php*
// @grant         GM_xmlhttpRequest
// @grant         GM_getValue
// @grant         GM_setValue
// @connect       api.torn.com
// @author        aquagloop
// @license       MIT
// ==/UserScript==

(function () {
    'use strict';

    const STORAGE_KEY = 'tornApiKey';

    const fetchWorkStats = (userId, apiKey) => {
        return new Promise((resolve, reject) => {
            GM_xmlhttpRequest({
                method: 'GET',
                url: `https://api.torn.com/v2/user/${userId}/hof?key=${apiKey}`,
                headers: {
                    Accept: 'application/json'
                },
                onload: (response) => {
                    try {
                        const data = JSON.parse(response.responseText);
                        if (data.error) {
                            reject(data.error.error);
                        } else {
                            resolve(data.hof?.working_stats?.value || 0);
                        }
                    } catch (e) {
                        reject('Failed to parse response');
                    }
                },
                onerror: () => reject('Network error')
            });
        });
    };

    const handleProfilePage = async (apiKey) => {
        const urlParams = new URLSearchParams(window.location.search);
        const userId = urlParams.get('XID');
        if (!userId) return;

        try {
            const totalStats = await fetchWorkStats(userId, apiKey);
            const jobLi = Array.from(document.querySelectorAll('li')).find(li => {
                const span = li.querySelector('.user-information-section span.bold');
                return span && span.textContent.trim() === 'Job';
            });

            if (jobLi) {
                const workstatsLi = document.createElement('li');
                workstatsLi.innerHTML = `<div class="user-information-section"><span class="bold">Work Stats</span></div><div class="user-info-value"><span>${totalStats.toLocaleString()}</span></div>`;
                jobLi.parentNode.insertBefore(workstatsLi, jobLi.nextSibling);
            }
        } catch (error) {
            console.error('Workstats error on profile page:', error);
        }
    };

    const handleCompanyPage = async (apiKey) => {
        const employeesList = document.querySelector('ul.employees-list');
        if (!employeesList) return;

        const employeeItems = employeesList.querySelectorAll('li');
        for (const item of employeeItems) {
            const userLink = item.querySelector('.employee a.user.name');
            if (userLink) {
                const match = userLink.href.match(/XID=(\d+)/);
                const userId = match ? match[1] : null;

                if (userId) {
                    try {
                        const totalStats = await fetchWorkStats(userId, apiKey);
                        const statusLi = item.querySelector('.status');
                        if (statusLi) {
                            const statsSpan = document.createElement('span');
                            statsSpan.style.marginLeft = '10px';
                            statsSpan.style.fontWeight = 'bold';
                            statsSpan.textContent = totalStats.toLocaleString();
                            statusLi.appendChild(statsSpan);
                        }
                    } catch (error) {
                        console.error(`Error fetching work stats for user ${userId}:`, error);
                    }
                }
            }
        }
    };

    const main = async () => {
        let apiKey = await GM_getValue(STORAGE_KEY, null);
        if (!apiKey) {
            apiKey = prompt('Enter your Torn API key:');
            if (!apiKey) {
                alert('No API key entered. Work stats will not be displayed.');
                return;
            }
            await GM_setValue(STORAGE_KEY, apiKey);
        }

        try {
            await fetchWorkStats('1', apiKey);

            if (window.location.pathname.includes('profiles.php')) {
                await handleProfilePage(apiKey);
            } else if (window.location.pathname.includes('joblist.php')) {
                await handleCompanyPage(apiKey);
            }
        } catch (error) {
            console.error('API key validation error:', error);
            if (error === 'Incorrect key' || error === 2) {
                await GM_setValue(STORAGE_KEY, null);
                alert('Invalid API key. Reload the page to enter a new one.');
            } else {
                alert(`Error: ${error}`);
            }
        }
    };

    const observer = new MutationObserver((mutations, obs) => {
        if (document.querySelector('.profile-container') || document.querySelector('.employees-list')) {
            main();
            obs.disconnect();
        }
    });

    observer.observe(document.body, {
        childList: true,
        subtree: true
    });
})();