// ==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
});
})();