Torn.com Mug Inactive Players

Add last action and job information to user search results

// ==UserScript==
// @name         Torn.com Mug Inactive Players
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  Add last action and job information to user search results
// @author       ShAdOwCrEsT [3929345]
// @match        https://www.torn.com/page.php?sid=UserList*
// @grant        GM_xmlhttpRequest
// @grant        GM_addStyle
// ==/UserScript==

(function() {
    'use strict';

    const API_KEY = prompt('Enter Your Public API key:');
    if (!API_KEY) {
        alert('API key is required for this script to work.');
        return;
    }

    GM_addStyle(`
        .search-enhancer-badge {
            display: inline;
            font-size: 10.5px;
            padding: 9px 10px;
            margin-left: 6px;
            border-radius: 2px;
            color: white;
            font-weight: bold;
        }

        .badge-online { background: #4CAF50; }
        .badge-idle { background: #FF9800; }
        .badge-offline { background: #666; }
        .badge-job-good { background: #4CAF50; margin-left: 2px; }
        .badge-job-bad { background: #f44336; margin-left: 2px; }
        .badge-loading { background: #999; }
        .badge-error { background: #f44336; }
    `);

    function extractUserId(imgElement) {
        const altText = imgElement.alt;
        const match = altText.match(/\[(\d+)\]/);
        return match ? match[1] : null;
    }

    function makeAPIRequest(url) {
        return new Promise((resolve, reject) => {
            GM_xmlhttpRequest({
                method: 'GET',
                url: url,
                onload: function(response) {
                    try {
                        const data = JSON.parse(response.responseText);
                        resolve(data);
                    } catch (e) {
                        reject(new Error('Failed to parse JSON'));
                    }
                },
                onerror: function() {
                    reject(new Error('Network error'));
                }
            });
        });
    }

    async function getUserProfile(userId) {
        const url = `https://api.torn.com/v2/user/${userId}/profile?striptags=true&key=${API_KEY}`;
        return await makeAPIRequest(url);
    }

    async function getUserJob(userId) {
        const url = `https://api.torn.com/v2/user/${userId}/job?key=${API_KEY}`;
        return await makeAPIRequest(url);
    }

    function parseTimeAgo(relative) {
        if (!relative || relative.toLowerCase().includes('online')) {
            return 'now';
        }

        const match = relative.match(/(\d+)\s*(min|hour|day|week|month|year)/i);
        if (!match) return '?';

        const num = parseInt(match[1]);
        const unit = match[2].toLowerCase();

        if (unit.startsWith('min')) return `${num}m`;
        if (unit.startsWith('hour')) return `${num}h`;
        if (unit.startsWith('day')) return `${num}d`;
        if (unit.startsWith('week')) return `${num * 7}d`;
        if (unit.startsWith('month')) return `${Math.round(num * 30)}d`;
        return `${Math.round(num * 365)}d`;
    }

    function getJobCategory(jobData) {  //Job category vala
        if (!jobData || !jobData.job) return 'N/A';

        const jobName = jobData.job.name.toLowerCase();
        if (jobName.includes('army')) return 'Army';
        if (jobName.includes('casino')) return 'Casino';
        if (jobName.includes('hospital') || jobName.includes('medical')) return 'Med';
        if (jobName.includes('law') || jobName.includes('police')) return 'Law';
        if (jobName.includes('grocer')) return 'Shop';
        if (jobName.includes('education') || jobName.includes('school')) return 'Edu';
        return 'Pvt';
    }

    function getStatusClass(lastAction) { // Status category...
        const status = lastAction.status.toLowerCase();
        if (status === 'online') return 'badge-online';
        if (status === 'idle') return 'badge-idle';
        return 'badge-offline';
    }

    function addBadgesToUser(userLink, userId) {

        if (userLink.dataset.enhanced === 'true') return;
        userLink.dataset.enhanced = 'true';

        const timeBadge = document.createElement('span');
        timeBadge.className = 'search-enhancer-badge badge-loading';
        timeBadge.textContent = '...';

        const jobBadge = document.createElement('span');
        jobBadge.className = 'search-enhancer-badge badge-job-bad';
        jobBadge.textContent = '...';

        userLink.parentNode.insertBefore(timeBadge, userLink.nextSibling);
        userLink.parentNode.insertBefore(jobBadge, timeBadge.nextSibling);

        Promise.all([
            getUserProfile(userId),
            getUserJob(userId)
        ]).then(([profileData, jobData]) => {

            if (profileData.profile && profileData.profile.last_action) {
                const timeAgo = parseTimeAgo(profileData.profile.last_action.relative);
                const statusClass = getStatusClass(profileData.profile.last_action);
                timeBadge.textContent = timeAgo;
                timeBadge.className = `search-enhancer-badge ${statusClass}`;
            } else {
                timeBadge.textContent = '?';
                timeBadge.className = 'search-enhancer-badge badge-error';
            }

            const jobText = getJobCategory(jobData);
            jobBadge.textContent = jobText;

            if (jobText === 'N/A' || jobText === 'Pvt') {
                jobBadge.className = 'search-enhancer-badge badge-job-bad';
            } else {
                jobBadge.className = 'search-enhancer-badge badge-job-good';
            }

        }).catch(error => {
            timeBadge.textContent = 'ERR';
            timeBadge.className = 'search-enhancer-badge badge-error';
            jobBadge.textContent = 'ERR';
            jobBadge.className = 'search-enhancer-badge badge-error';
        });
    }

    function processSearchResults() {

        const userLinks = document.querySelectorAll('a.user.name[href*="profiles.php?XID="]');

        userLinks.forEach(userLink => {
            const match = userLink.href.match(/XID=(\d+)/);
            if (match) {
                const userId = match[1];
                addBadgesToUser(userLink, userId);
            }
        });
    }

    setTimeout(processSearchResults, 1000);

    const observer = new MutationObserver(function(mutations) {
        let shouldProcess = false;
        mutations.forEach(function(mutation) {
            if (mutation.addedNodes.length > 0) {
                shouldProcess = true;
            }
        });

        if (shouldProcess) {
            setTimeout(processSearchResults, 500);
        }
    });

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

    console.log('Torn.com User Search Enhancer loaded - minimal badges');
})();