您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Adds a dark-mode 'Copy' button with a customizable settings menu next to a user's name.
当前为
// ==UserScript== // @name Torn Profile Link Formatter // @namespace GNSC4 [268863] // @version 1.1.1 // @description Adds a dark-mode 'Copy' button with a customizable settings menu next to a user's name. // @author GNSC4 [268863] // @match https://www.torn.com/profiles.php?XID=* // @grant GM_addStyle // @grant GM_setValue // @grant GM_getValue // ==/UserScript== (function() { 'use strict'; // --- GM Polyfills for environments without native support --- const GNSC_setValue = typeof GM_setValue !== 'undefined' ? GM_setValue : (key, value) => localStorage.setItem(key, JSON.stringify(value)); const GNSC_getValue = typeof GM_getValue !== 'undefined' ? GM_getValue : (key, def) => JSON.parse(localStorage.getItem(key)) || def; // --- Add Styles for the UI --- if (typeof GM_addStyle !== 'undefined') { GM_addStyle(` .gnsc-copy-container { display: inline-flex; align-items: center; vertical-align: middle; gap: 5px; margin-left: 10px; } .gnsc-btn { background-color: #333; color: #DDD; border: 1px solid #555; border-radius: 5px; padding: 3px 8px; text-decoration: none; font-size: 12px; line-height: 1.5; font-weight: bold; cursor: pointer; } .gnsc-btn:hover { background-color: #444; } .gnsc-settings-panel { display: none; position: absolute; background-color: #2c2c2c; border: 1px solid #555; border-radius: 5px; padding: 10px; z-index: 1000; top: 100%; left: 0; min-width: 150px; } .gnsc-settings-panel div { margin-bottom: 5px; display: flex; align-items: center; } .gnsc-settings-panel label { color: #DDD; flex-grow: 1; } .gnsc-settings-panel input { margin-left: 5px; } .gnsc-settings-panel label.disabled { color: #888; } .gnsc-settings-container { position: relative; } `); } /** * Main script logic, called after elements are confirmed to exist. * @param {HTMLElement} nameElement The user's name header element (h4). * @param {HTMLElement} infoTable The 'ul' element containing the user info rows. */ function main(nameElement, infoTable) { // --- 1. Extract User Information --- const urlParams = new URLSearchParams(window.location.search); const userId = urlParams.get('XID'); if (!userId) { console.error("Torn Profile Link Formatter: Could not find User ID."); return; } const userName = nameElement.textContent.split(' [')[0].trim(); let factionLinkEl = null; let companyLinkEl = null; const infoListItems = infoTable.querySelectorAll('li'); infoListItems.forEach(item => { const titleEl = item.querySelector('.user-information-section .bold'); if (!titleEl) return; const title = titleEl.textContent.trim(); if (title === 'Faction') { factionLinkEl = item.querySelector('.user-info-value a'); } else if (title === 'Job') { companyLinkEl = item.querySelector('.user-info-value a'); } }); const userInfo = { id: userId, name: userName, profileUrl: `https://www.torn.com/profiles.php?XID=${userId}`, attackUrl: `https://www.torn.com/loader2.php?sid=getInAttack&user2ID=${userId}`, factionUrl: factionLinkEl ? factionLinkEl.href : null, companyUrl: companyLinkEl ? companyLinkEl.href : null, }; // --- 2. Create UI Elements --- createUI(nameElement, userInfo); } /** * Creates and injects all UI elements (buttons, panel). * @param {HTMLElement} targetElement - The element to insert the UI after. * @param {object} userInfo - Object containing user data. */ function createUI(targetElement, userInfo) { const container = document.createElement('div'); container.className = 'gnsc-copy-container'; const copyButton = document.createElement('a'); copyButton.href = "#"; copyButton.className = 'gnsc-btn'; copyButton.innerHTML = '<span>Copy</span>'; copyButton.addEventListener('click', (e) => handleCopyClick(e, copyButton, userInfo)); const settingsContainer = document.createElement('div'); settingsContainer.className = 'gnsc-settings-container'; const settingsButton = document.createElement('a'); settingsButton.href = "#"; settingsButton.className = 'gnsc-btn'; settingsButton.innerHTML = '⚙️'; const settingsPanel = createSettingsPanel(userInfo); settingsButton.addEventListener('click', (e) => { e.preventDefault(); settingsPanel.style.display = settingsPanel.style.display === 'block' ? 'none' : 'block'; }); document.addEventListener('click', (e) => { if (!settingsContainer.contains(e.target)) { settingsPanel.style.display = 'none'; } }); settingsContainer.appendChild(settingsButton); settingsContainer.appendChild(settingsPanel); container.appendChild(copyButton); container.appendChild(settingsContainer); targetElement.insertAdjacentElement('afterend', container); } /** * Creates the settings panel element. * @param {object} userInfo - Object containing user data to check for link availability. * @returns {HTMLElement} The settings panel element. */ function createSettingsPanel(userInfo) { const panel = document.createElement('div'); panel.className = 'gnsc-settings-panel'; const settings = loadSettings(); const options = [ { key: 'profile', label: 'Profile', available: true }, { key: 'attack', label: 'Attack', available: true }, { key: 'faction', label: 'Faction', available: !!userInfo.factionUrl }, { key: 'company', label: 'Company', available: !!userInfo.companyUrl }, ]; options.forEach(option => { const wrapper = document.createElement('div'); const checkbox = document.createElement('input'); const label = document.createElement('label'); checkbox.type = 'checkbox'; checkbox.id = `gnsc-check-${option.key}`; checkbox.checked = option.available && settings[option.key]; checkbox.disabled = !option.available; checkbox.addEventListener('change', () => saveSettings()); label.htmlFor = `gnsc-check-${option.key}`; label.textContent = option.label; if (!option.available) { label.classList.add('disabled'); } wrapper.appendChild(label); wrapper.appendChild(checkbox); panel.appendChild(wrapper); }); return panel; } /** * Handles the logic when the copy button is clicked. */ function handleCopyClick(e, button, userInfo) { e.preventDefault(); const settings = loadSettings(); const links = []; if (settings.profile) links.push(`<a href="${userInfo.profileUrl}">Profile</a>`); if (settings.attack) links.push(`<a href="${userInfo.attackUrl}">Attack</a>`); if (settings.faction && userInfo.factionUrl) links.push(`<a href="${userInfo.factionUrl}">Faction</a>`); if (settings.company && userInfo.companyUrl) links.push(`<a href="${userInfo.companyUrl}">Company</a>`); const formattedString = links.length > 0 ? `${userInfo.name} - ${links.join(' - ')}` : userInfo.name; copyToClipboard(formattedString); const originalText = button.innerHTML; button.innerHTML = '<span>Copied!</span>'; button.style.backgroundColor = '#2a633a'; setTimeout(() => { button.innerHTML = originalText; button.style.backgroundColor = ''; }, 2000); } // --- Data and Utility Functions --- function loadSettings() { return GNSC_getValue('tornProfileFormatterSettings', { profile: true, attack: true, faction: false, company: false }); } function saveSettings() { const settings = { profile: document.getElementById('gnsc-check-profile').checked, attack: document.getElementById('gnsc-check-attack').checked, faction: document.getElementById('gnsc-check-faction')?.checked || false, company: document.getElementById('gnsc-check-company')?.checked || false, }; GNSC_setValue('tornProfileFormatterSettings', settings); } function copyToClipboard(text) { const tempTextarea = document.createElement('textarea'); tempTextarea.style.position = 'fixed'; tempTextarea.style.left = '-9999px'; tempTextarea.value = text; document.body.appendChild(tempTextarea); tempTextarea.select(); document.execCommand('copy'); document.body.removeChild(tempTextarea); } // --- Script Entry Point --- // Use a MutationObserver to react instantly when the content loads. const observer = new MutationObserver((mutations, obs) => { const nameElement = document.querySelector('#skip-to-content'); const infoTable = document.querySelector('.basic-information .info-table'); const alreadyInjected = document.querySelector('.gnsc-copy-container'); if (nameElement && infoTable && infoTable.children.length > 5 && !alreadyInjected) { main(nameElement, infoTable); obs.disconnect(); // Stop observing once the UI is injected. } }); observer.observe(document.body, { childList: true, subtree: true }); })();