您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
NST: Loadout reveal for those in gunshops
// ==UserScript== // @name Loadout Reveal // @namespace http://tampermonkey.net/ // @version 0.3 // @description NST: Loadout reveal for those in gunshops // @author Hwa [2466470] // @match https://www.torn.com/loader.php?sid=attack* // @icon https://www.google.com/s2/favicons?sz=64&domain=torn.com // @grant none // @license MIT // ==/UserScript== (function() { 'use strict'; let alreadyExtracted = false; function debounce(func, delay) { let timeout; return (...args) => { clearTimeout(timeout); timeout = setTimeout(() => func(...args), delay); }; } function getArmorPrefix(armor) { const parts = ['helmet', 'chest', 'gloves', 'pants', 'boots']; const names = parts.map(part => armor[part]); // Return null if any part is missing if (names.some(name => !name)) return null; const prefixes = names.map(name => name.split(' ')[0]); const allSame = prefixes.every(prefix => prefix === prefixes[0]); return allSame ? prefixes[0] : null; } function formatWeaponData(weaponData) { if (!weaponData || typeof weaponData !== 'object') return "N/A"; const { weapon, damage, accuracy, bonus_attachments } = weaponData; if (!weapon) return "N/A"; let mainLine = `${weapon}, damage: ${damage}, accuracy: ${accuracy}`; let bonusesText = ''; if (Array.isArray(bonus_attachments) && bonus_attachments.length > 0) { bonusesText = '<br>Bonuses:'; for (const bonus of bonus_attachments) { const title = bonus.title.toLowerCase(); const description = bonus.description.replace('+', '').trim(); bonusesText += `<br>- ${title} (${description})`; } } return mainLine + bonusesText; } function createDataTable(dataObj) { const container = document.createElement('div'); container.style.display = 'flex'; container.style.alignItems = 'flex-start'; container.style.marginBottom = '10px'; container.style.gap = '10px'; // space between table and button const table = document.createElement('table'); table.id = "player_loadout_table"; table.style.borderCollapse = 'collapse'; table.style.background = '#111'; table.style.color = '#fff'; table.style.fontFamily = 'monospace'; table.style.zIndex = '9999'; table.style.position = 'relative'; for (const [key, value] of Object.entries(dataObj)) { const row = document.createElement('tr'); const keyCell = document.createElement('td'); keyCell.textContent = key; keyCell.style.border = '1px solid #888'; keyCell.style.padding = '4px'; keyCell.style.color = '#fff'; keyCell.style.background = '#222'; const valueCell = document.createElement('td'); if (value && typeof value === 'object' && value.weapon) { valueCell.innerHTML = formatWeaponData(value); } else if (typeof value === 'object') { // For other objects, just stringify nicely with indentation valueCell.textContent = JSON.stringify(value, null, 2); } else { valueCell.textContent = value; } valueCell.style.border = '1px solid #888'; valueCell.style.padding = '4px'; valueCell.style.color = '#fff'; valueCell.style.background = '#333'; row.appendChild(keyCell); row.appendChild(valueCell); table.appendChild(row); } const button = document.createElement('button'); button.textContent = 'Copy'; button.style.marginLeft = '5px'; button.style.padding = '2px 5px'; button.style.backgroundColor = '#444'; button.style.color = '#fff'; button.style.border = '1px solid #888'; button.style.borderRadius = '4px'; button.style.cursor = 'pointer'; button.style.fontFamily = 'monospace'; button.style.fontSize = '14px'; // Copy logic button.addEventListener('click', () => { const rows = Array.from(table.querySelectorAll('tr')).map(row => { const cells = row.querySelectorAll('td'); const label = cells[0].textContent.trim(); const content = cells[1].innerText.trim(); // innerText preserves line breaks return `${label}: ${content}`; }); const text = rows.join('\n'); navigator.clipboard.writeText(text).then(() => { button.textContent = 'Copied!'; setTimeout(() => (button.textContent = 'Copy'), 2000); }); }); // Append table and button to container container.appendChild(table); container.appendChild(button); return container; } function insertTable(data) { const table = createDataTable(data); const container = document.querySelector('[class^="coreWrap"]') const childToInsertAfter = container.querySelector('[class^="logStatsWrap__"]'); // choose the child element you want if (childToInsertAfter && container) { // Insert after the chosen child: childToInsertAfter.after(table); } else if (container) { // fallback: append at the end container.appendChild(table); } } function extractData() { if (alreadyExtracted) return; const players = document.querySelectorAll('[class^="player___"]'); let secondPlayer = null; // Check if there are at least two elements if (players.length >= 2) { secondPlayer = players[1]; // 0-based index, so 1 is the second element console.log("Second player found"); } else { console.log("Less than two elements found"); } if (secondPlayer) { alreadyExtracted = true; const nameEl = secondPlayer.querySelector('[id^="playername_"]'); let xid = "N/A"; if (xid === "N/A" && window.location.href.includes('user2ID=')) { const match = window.location.href.match(/user2ID=(\d+)/); if (match) xid = match[1]; } const armorAreas = secondPlayer.querySelectorAll('area'); const armor = {}; ['Helmet', 'Chest', 'Gloves', 'Pants', 'Boots'].forEach(part => { const area = Array.from(armorAreas).find(el => el.alt === part); if (area) { armor[part.toLowerCase()] = area.title || "N/A"; } }); const weaponListDiv = secondPlayer.querySelector('div[class^="weaponList"]'); const weaponMainDiv = weaponListDiv.querySelector('#weapon_main'); let primary = {}; if (weaponMainDiv) { // Weapon name from img alt (replace spaces with hyphen or keep as is) const weaponImg = weaponMainDiv.querySelector('figure[class^="weaponImage"] img'); const weaponName = weaponImg ? weaponImg.alt.trim() : "N/A"; // Damage value const damageSpan = weaponMainDiv.querySelector('[id^="damage-value_"]'); const damage = damageSpan ? damageSpan.textContent.trim() : "N/A"; // Accuracy value const accuracySpan = weaponMainDiv.querySelector('span[id^="accuracy_"]'); const accuracy = accuracySpan ? accuracySpan.textContent.trim() : "N/A"; // Bonus attachments (titles and descriptions) const bonusAttachments = [...weaponMainDiv.querySelectorAll('[class^="props"] i[data-bonus-attachment-title]')] .map(i => { return { title: i.getAttribute('data-bonus-attachment-title') || "", description: i.getAttribute('data-bonus-attachment-description') || "" }; }) // filter out empty titles if needed .filter(b => b.title); primary = { weapon: weaponName, damage: damage, accuracy: accuracy, bonus_attachments: bonusAttachments }; } const weaponSecondaryDiv = weaponListDiv.querySelector('#weapon_second'); let secondary = {}; if (weaponSecondaryDiv) { // Weapon name from img alt (replace spaces with hyphen or keep as is) const weaponImg = weaponSecondaryDiv.querySelector('figure[class^="weaponImage"] img'); const weaponName = weaponImg ? weaponImg.alt.trim() : "N/A"; // Damage value const damageSpan = weaponSecondaryDiv.querySelector('[id^="damage-value_"]'); const damage = damageSpan ? damageSpan.textContent.trim() : "N/A"; // Accuracy value const accuracySpan = weaponSecondaryDiv.querySelector('span[id^="accuracy_"]'); const accuracy = accuracySpan ? accuracySpan.textContent.trim() : "N/A"; // Bonus attachments (titles and descriptions) const bonusAttachments = [...weaponSecondaryDiv.querySelectorAll('[class^="props"] i[data-bonus-attachment-title]')] .map(i => { return { title: i.getAttribute('data-bonus-attachment-title') || "", description: i.getAttribute('data-bonus-attachment-description') || "" }; }) // filter out empty titles if needed .filter(b => b.title); secondary = { weapon: weaponName, damage: damage, accuracy: accuracy, bonus_attachments: bonusAttachments }; } const weaponMeleeDiv = weaponListDiv.querySelector('#weapon_melee'); let melee = {}; if (weaponMeleeDiv) { // Weapon name from img alt (replace spaces with hyphen or keep as is) const weaponImg = weaponMeleeDiv.querySelector('figure[class^="weaponImage"] img'); const weaponName = weaponImg ? weaponImg.alt.trim() : "N/A"; // Damage value const damageSpan = weaponMeleeDiv.querySelector('[id^="damage-value_"]'); const damage = damageSpan ? damageSpan.textContent.trim() : "N/A"; // Accuracy value const accuracySpan = weaponMeleeDiv.querySelector('span[id^="accuracy_"]'); const accuracy = accuracySpan ? accuracySpan.textContent.trim() : "N/A"; // Bonus attachments (titles and descriptions) const bonusAttachments = [...weaponMeleeDiv.querySelectorAll('[class^="props"] i[data-bonus-attachment-title]')] .map(i => { return { title: i.getAttribute('data-bonus-attachment-title') || "", description: i.getAttribute('data-bonus-attachment-description') || "" }; }) // filter out empty titles if needed .filter(b => b.title); melee = { weapon: weaponName, damage: damage, accuracy: accuracy, bonus_attachments: bonusAttachments }; } const weaponTempDiv = weaponListDiv.querySelector('#weapon_temp'); const weaponTemp = weaponTempDiv?.querySelector('figure[class^="weaponImage"] img')?.alt.trim() || "N/A"; const armorPrefix = getArmorPrefix(armor); const data = { Name: nameEl ? `${nameEl.textContent.trim()} [${xid}]` : "N/A", ...(armorPrefix ? { "Full Set": armorPrefix } : { Helmet: armor.helmet || "N/A", Chest: armor.chest || "N/A", Gloves: armor.gloves || "N/A", Pants: armor.pants || "N/A", Boots: armor.boots || "N/A" }), Primary: (primary.weapon && primary.weapon.trim() !== "") ? primary : "N/A", Secondary: (secondary.weapon && secondary.weapon.trim() !== "") ? secondary : "N/A", Melee: (melee.weapon && melee.weapon.trim() !== "") ? melee : "N/A", Temp: weaponTemp, }; console.log("✅ Torn Fight Report Extracted Data:", data); return data } } const observer = new MutationObserver( debounce(() => { const nameElement = document.querySelector('[id^="playername_"]'); const playerElements = document.querySelectorAll('[class^="player___"]'); if (nameElement && playerElements.length >= 2) { console.log("Checking!") const indicatorsWrap = playerElements[1].querySelector('div[class^="indicatorsContainer"]'); if (indicatorsWrap && indicatorsWrap.children.length > 0) { observer.disconnect(); const data = extractData(); insertTable(data); } } }, 1000) // only checks every 1s ); observer.observe(document.body, { childList: true, subtree: true }); })();