Add last action and job information to user search results
当前为
// ==UserScript==
// @name Torn.com Mug Inactive Players
// @namespace http://tampermonkey.net/
// @version 1.2
// @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
// @grant GM_setValue
// @grant GM_getValue
// ==/UserScript==
(function() {
'use strict';
let API_KEY = GM_getValue('torn_api_key', '');
if (!API_KEY) {
API_KEY = prompt('Enter Your Public API key:');
if (!API_KEY) {
alert('API key is required for this script to work.');
return;
}
GM_setValue('torn_api_key', API_KEY);
}
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) {
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 'Grocer';
if (jobName.includes('education') || jobName.includes('school')) return 'Edu';
if (jobName.includes('adult novelties')) return 'AN';
if (jobName.includes('amusement park')) return 'AP';
if (jobName.includes('candle shop')) return 'CS';
if (jobName.includes('car dealership')) return 'CD';
if (jobName.includes('clothing store')) return 'CS';
if (jobName.includes('cruise line')) return 'CL';
if (jobName.includes('cyber cafe')) return 'CC';
if (jobName.includes('detective agency')) return 'DA';
if (jobName.includes('farm')) return 'Farm';
if (jobName.includes('firework stand')) return 'FS';
if (jobName.includes('fitness center')) return 'FC';
if (jobName.includes('flower shop')) return 'FS';
if (jobName.includes('furniture store')) return 'FS';
if (jobName.includes('game shop')) return 'GS';
if (jobName.includes('gas station')) return 'GS';
if (jobName.includes('gents strip club')) return 'GSC';
if (jobName.includes('grocery store')) return 'GS';
if (jobName.includes('gun shop')) return 'GS';
if (jobName.includes('hair salon')) return 'HS';
if (jobName.includes('ladies strip club')) return 'LSC';
if (jobName.includes('law firm')) return 'LF';
if (jobName.includes('lingerie store')) return 'LS';
if (jobName.includes('logistics management')) return 'LM';
if (jobName.includes('meat warehouse')) return 'MW';
if (jobName.includes('mechanic shop')) return 'MS';
if (jobName.includes('mining corporation')) return 'MC';
if (jobName.includes('music store')) return 'MS';
if (jobName.includes('nightclub')) return 'Night';
if (jobName.includes('oil rig')) return 'OR';
if (jobName.includes('private security')) return 'PS';
if (jobName.includes('property broker')) return 'PB';
if (jobName.includes('pub')) return 'Pub';
if (jobName.includes('restaurant')) return 'Rest';
if (jobName.includes('software corporation')) return 'SC';
if (jobName.includes('sweet shop')) return 'SS';
if (jobName.includes('television network')) return 'TV';
if (jobName.includes('theater')) return 'Thtr';
if (jobName.includes('toy shop')) return 'TS';
if (jobName.includes('zoo')) return 'Zoo';
return 'N/A';
}
function getStatusClass(lastAction) {
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 === 'Army' || jobText === 'Casino' || jobText === 'Med' ||
jobText === 'Law' || jobText === 'Grocer' || jobText === 'Edu') {
jobBadge.className = 'search-enhancer-badge badge-job-good';
} else {
jobBadge.className = 'search-enhancer-badge badge-job-bad';
}
}).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');
})();