您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Add customizable buttons to Torn.com chat with enhanced UI and features
当前为
// ==UserScript== // @name Torn.com Enhanced Chat Buttons V2 // @namespace http://tampermonkey.net/ // @version 2.41 // @description Add customizable buttons to Torn.com chat with enhanced UI and features // @author Created by Callz [2188704], updated by Weav3r [1853324] // @match https://www.torn.com/* // @grant GM_setClipboard // ==/UserScript== (function() { 'use strict'; const CACHE_TTL = 24 * 60 * 60 * 1000; const buttonCSS = ` .custom-chat-button { background-color: #007BFF; color: white; padding: 2px 7px; text-align: center; text-decoration: none; display: inline-block; font-size: 14px; margin: 4px 6px; cursor: pointer; border-radius: 5px; border: none; transition: transform 0.1s ease, box-shadow 0.1s ease; min-width: 80px; overflow: hidden; white-space: nowrap; } .custom-chat-button:active { transform: scale(0.95); box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.2); } .custom-chat-button.recent { border: 2px solid #FFD700; box-shadow: 0 0 5px rgba(255, 215, 0, 0.8); } .custom-ui-panel { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background-color: #f5f5f5; padding: 10px; color: black; border-radius: 10px; box-shadow: 0 0 10px rgba(0, 0, 0, 0.2); z-index: 9999999999; width: 90%; max-width: 500px; box-sizing: border-box; max-height: 90vh; overflow: auto; } .custom-ui-panel h3 { font-size: 20px; margin-bottom: 10px; text-align: center; } .custom-ui-panel label { font-size: 14px; margin-bottom: 5px; display: block; } .custom-ui-panel input[type="text"], .custom-ui-panel select, .custom-ui-panel textarea { width: calc(100% - 12px); padding: 5px; margin-bottom: 10px; border: 1px solid #ccc; border-radius: 5px; font-size: 14px; } .custom-ui-panel input[type="color"] { padding: 0; margin-top: 5px; border: none; } .custom-ui-panel button { background-color: #007BFF; color: white; border: none; padding: 8px 12px; border-radius: 5px; cursor: pointer; margin: 5px; font-size: 14px; transition: background-color 0.3s ease; } .custom-ui-panel button#close-ui { background-color: #ccc; } .custom-ui-panel button#close-ui:hover { background-color: #999; } .custom-ui-panel textarea { height: 60px; resize: vertical; position: relative; } .custom-ui-panel hr { margin: 10px 0; border: 0; border-top: 1px solid #ccc; } .char-counter { position: absolute; bottom: 10px; right: 10px; font-size: 12px; color: #999; } #chat-config-button { color: green; } #button-configs { max-height: 400px; overflow-y: auto; margin-bottom: 10px; } @media (max-width: 600px) { .custom-ui-panel { width: 95%; max-width: none; padding: 8px; } .custom-ui-panel h3 { font-size: 18px; } .custom-ui-panel label, .custom-ui-panel button { font-size: 12px; } .custom-ui-panel input[type="text"], .custom-ui-panel select, .custom-ui-panel textarea { font-size: 12px; } .custom-ui-panel button { padding: 6px 10px; } .char-counter { font-size: 10px; } } .tabs { display: flex; margin-bottom: 10px; } .tab { flex: 1; padding: 10px; cursor: pointer; text-align: center; background-color: #e9e9e9; border: 1px solid #ccc; border-bottom: none; border-radius: 10px 10px 0 0; } .tab.settings-tab { flex: 0.2; padding: 10px; cursor: pointer; text-align: center; background-color: #e9e9e9; border: 1px solid #ccc; border-bottom: none; border-radius: 10px 10px 0 0; } .tab.active { background-color: #fff; border-bottom: 1px solid #fff; } .tab-content { display: none; } .tab-content.active { display: block; } .custom-ui-panel.config-list-tab-active { max-height: 80vh; } .search-container { display: flex; margin-bottom: 10px; } .search-container input[type="text"] { flex: 3; padding: 5px; margin-right: 5px; } .search-container select { flex: 1; padding: 5px; } .highlight { background-color: yellow; } .tt-chat-filter { display: flex; padding: 4px; align-items: center; background-color: var(--chat-box-bg); color: var(--chat-box-label-info); border-bottom: 1px solid var(--chat-box-input-border); margin-bottom: 8px; } .tt-chat-filter input { margin-left: 4px; margin-right: 4px; border-radius: 5px; width: -webkit-fill-available; width: -moz-available; border: 1px solid var(--chat-box-input-border); background-color: var(--chat-box-bg); color: var(--chat-box-label-info); } .notification { position: fixed; bottom: 20px; right: 20px; background-color: rgba(0, 0, 0, 0.8); color: white; padding: 10px; border-radius: 5px; z-index: 9999999999; opacity: 0; transition: opacity 0.5s ease; } .notification.show { opacity: 1; } .button-config-card { border: 1px solid #ccc; background-color: #fff; padding: 10px; margin: 10px 0; border-radius: 5px; } .button-config-message { white-space: pre-wrap; background: #f9f9f9; padding: 5px; border-radius: 5px; border: 1px solid #ddd; margin: 5px 0; } `; const conditions = { TradeChat: chatBox => { // New chat version if (chatBox.id === 'public_trade') return true; // Old chat version const oldTitle = chatBox.querySelector('.chat-box-header__name___jIjjM'); return oldTitle && oldTitle.textContent === 'Trade'; }, HospitalChat: chatBox => { // New chat version if (chatBox.id === 'public_hospital') return true; // Old chat version const oldTitle = chatBox.querySelector('.chat-box-header__name___jIjjM'); return oldTitle && oldTitle.textContent === 'Hospital'; }, FactionChat: chatBox => { // New chat version if (chatBox.id && chatBox.id.startsWith('faction-')) return true; // Old chat version const oldTitle = chatBox.querySelector('.chat-box-header__name___jIjjM'); return oldTitle && oldTitle.textContent === 'Faction'; }, CompanyChat: chatBox => { // New chat version if (chatBox.id && chatBox.id.startsWith('company-')) return true; // Old chat version const oldTitle = chatBox.querySelector('.chat-box-header__name___jIjjM'); return oldTitle && oldTitle.textContent === 'Company'; }, GlobalChat: chatBox => { // New chat version if (chatBox.id === 'public_global') return true; // Old chat version const oldTitle = chatBox.querySelector('.chat-box-header__name___jIjjM'); return oldTitle && oldTitle.textContent === 'Global'; }, UserChat: chatBox => { // New chat version if (chatBox.id && chatBox.id.startsWith('private-')) return true; // Old chat version return chatBox.querySelector('.chat-box-header__options___nTsMU') !== null; } }; const companyTypes = { 1: "Hair Salon", 2: "Law Firm", 3: "Flower Shop", 4: "Car Dealership", 5: "Clothing Store", 6: "Gun Shop", 7: "Game Shop", 8: "Candle Shop", 9: "Toy Shop", 10: "Adult Novelties", 11: "Cyber Cafe", 12: "Grocery Store", 13: "Theater", 14: "Sweet Shop", 15: "Cruise Line", 16: "Television Network", 18: "Zoo", 19: "Firework Stand", 20: "Property Broker", 21: "Furniture Store", 22: "Gas Station", 23: "Music Store", 24: "Nightclub", 25: "Pub", 26: "Gents Strip Club", 27: "Restaurant", 28: "Oil Rig", 29: "Fitness Center", 30: "Mechanic Shop", 31: "Amusement Park", 32: "Lingerie Store", 33: "Meat Warehouse", 34: "Farm", 35: "Software Corporation", 36: "Ladies Strip Club", 37: "Private Security Firm", 38: "Mining Corporation", 39: "Detective Agency", 40: "Logistics Management", }; function addCSS(cssString) { const style = document.createElement('style'); style.textContent = cssString; document.head.append(style); } function showNotification(message) { const notification = document.createElement('div'); notification.className = 'notification'; notification.innerText = message; document.body.appendChild(notification); requestAnimationFrame(() => { notification.classList.add('show'); }); setTimeout(() => { notification.classList.remove('show'); setTimeout(() => { notification.remove(); }, 500); }, 2000); } function saveRecentButtonInfo(buttonText, chatBoxName) { localStorage.setItem('recentButtonInfo', JSON.stringify({ buttonText, chatBoxName })); } function clearRecentButtonInfo() { localStorage.removeItem('recentButtonInfo'); } function getButtonConfigurations() { return JSON.parse(localStorage.getItem('chatButtonConfig')) || { buttons: [] }; } function saveButtonConfigurations(config) { localStorage.setItem('chatButtonConfig', JSON.stringify(config)); } function getAPIKey() { return localStorage.getItem('apiKey') || ''; } function saveAPIKey(key) { localStorage.setItem('apiKey', key); showNotification('API key saved.'); } function saveCache(key, data) { const cacheData = { timestamp: Date.now(), data }; localStorage.setItem(key, JSON.stringify(cacheData)); } function loadCache(key) { const cacheData = JSON.parse(localStorage.getItem(key)); if (cacheData && (Date.now() - cacheData.timestamp < CACHE_TTL)) { return cacheData.data; } return null; } function clearCache() { localStorage.removeItem('companyCache'); localStorage.removeItem('factionCache'); showNotification('API cache cleared.'); } function createUIPanel() { if (document.querySelector('.custom-ui-panel')) { return; } const panel = document.createElement('div'); panel.className = 'custom-ui-panel'; panel.innerHTML = ` <div class="tabs"> <div class="tab active" data-tab="config-list-tab">Configured Buttons</div> <div class="tab" data-tab="config-edit-tab">Create/Edit Button</div> <div class="tab settings-tab" data-tab="config-settings-tab">⚙️</div> </div> <div id="config-list-tab" class="tab-content active"> <div class="search-container"> <input type="text" id="search-input" placeholder="Search..."> <select id="search-select"> <option value="buttonText">Text</option> <option value="condition">Condition</option> <option value="text">Message</option> </select> </div> <div id="button-configs"></div> </div> <div id="config-edit-tab" class="tab-content"> <div> <label for="button-text">Button Text</label> <input type="text" id="button-text" placeholder="Button Text"> <label for="button-color">Background Color</label> <input type="color" id="button-color"> <label for="button-condition">Condition</label> <select id="button-condition"> <option value="TradeChat">Trade Chat</option> <option value="HospitalChat">Hospital Chat</option> <option value="FactionChat">Faction Chat</option> <option value="CompanyChat">Company Chat</option> <option value="GlobalChat">Global Chat</option> <option value="UserChat">User Chat</option> </select> <label for="button-text-content">Message</label> <textarea id="button-text-content" placeholder="Enter your message here. Use {name} for chatter's name, {company} for company info, {faction} for faction info."></textarea> <div class="char-counter" id="char-counter">0</div> <button id="add-button">Add Button</button> <button id="edit-button" style="display: none;">Save Button</button> </div> </div> <div id="config-settings-tab" class="tab-content"> <label for="api-key">API Key</label> <input type="text" id="api-key" placeholder="Enter your API key" value="${getAPIKey()}"> <button id="save-api-key-button">Save API Key</button> <button id="import-button">Import Config (File)</button> <button id="export-button">Export Config (File)</button> <button id="clear-cache-button">Clear API Cache</button> </div> <button id="close-ui">Close</button> `; document.body.appendChild(panel); document.querySelectorAll('.tab').forEach(tab => { tab.addEventListener('click', () => { switchTab(tab.dataset.tab); }); }); document.getElementById('add-button').addEventListener('click', addNewButtonConfig); document.getElementById('edit-button').addEventListener('click', editButtonConfig); document.getElementById('close-ui').addEventListener('click', closeUI); document.getElementById('import-button').addEventListener('click', importConfig); document.getElementById('export-button').addEventListener('click', exportConfig); document.getElementById('clear-cache-button').addEventListener('click', clearCache); document.getElementById('button-text-content').addEventListener('input', updateCharCounter); document.getElementById('search-input').addEventListener('input', filterButtonConfigs); document.getElementById('save-api-key-button').addEventListener('click', () => { const key = document.getElementById('api-key').value; saveAPIKey(key); }); populateButtonConfigs(); } function switchTab(tabId) { document.querySelectorAll('.tab, .tab-content').forEach(el => { el.classList.remove('active'); }); document.querySelector(`[data-tab="${tabId}"]`).classList.add('active'); document.getElementById(tabId).classList.add('active'); const panel = document.querySelector('.custom-ui-panel'); if (tabId === 'config-list-tab') { panel.classList.add('config-list-tab-active'); } else { panel.classList.remove('config-list-tab-active'); } } function populateButtonConfigs() { const configsContainer = document.getElementById('button-configs'); configsContainer.innerHTML = ''; const configs = getButtonConfigurations(); configs.buttons.forEach((buttonConfig, index) => { const configDiv = document.createElement('div'); configDiv.className = 'button-config-card draggable'; configDiv.dataset.index = index; const textDiv = document.createElement('div'); textDiv.innerHTML = `<strong>Text:</strong> ${buttonConfig.buttonText}`; configDiv.appendChild(textDiv); const colorDiv = document.createElement('div'); colorDiv.innerHTML = `<strong>Color:</strong> ${buttonConfig.backgroundColor}`; configDiv.appendChild(colorDiv); const conditionDiv = document.createElement('div'); conditionDiv.innerHTML = `<strong>Condition:</strong> ${buttonConfig.condition}`; configDiv.appendChild(conditionDiv); const messageDiv = document.createElement('div'); messageDiv.className = 'button-config-message'; messageDiv.innerText = buttonConfig.text; configDiv.appendChild(messageDiv); const editButton = document.createElement('button'); editButton.textContent = 'Edit'; editButton.addEventListener('click', () => { selectForEdit(index); switchTab('config-edit-tab'); }); configDiv.appendChild(editButton); const deleteButton = document.createElement('button'); deleteButton.textContent = 'Delete'; deleteButton.addEventListener('click', () => deleteButtonConfig(index)); configDiv.appendChild(deleteButton); const moveUpButton = document.createElement('button'); moveUpButton.textContent = 'Up'; moveUpButton.addEventListener('click', () => moveButtonConfig(index, -1)); configDiv.appendChild(moveUpButton); const moveDownButton = document.createElement('button'); moveDownButton.textContent = 'Down'; moveDownButton.addEventListener('click', () => moveButtonConfig(index, 1)); configDiv.appendChild(moveDownButton); configsContainer.appendChild(configDiv); }); } function filterButtonConfigs() { const searchInput = document.getElementById('search-input').value.toLowerCase(); const searchBy = document.getElementById('search-select').value; const configs = getButtonConfigurations(); const filteredConfigs = configs.buttons.filter(buttonConfig => { const fieldValue = buttonConfig[searchBy].toLowerCase(); return fieldValue.includes(searchInput); }); const configsContainer = document.getElementById('button-configs'); configsContainer.innerHTML = ''; filteredConfigs.forEach((buttonConfig, index) => { const configDiv = document.createElement('div'); configDiv.className = 'button-config-card draggable'; configDiv.dataset.index = index; const textDiv = document.createElement('div'); textDiv.innerHTML = `<strong>Text:</strong> ${buttonConfig.buttonText}`; configDiv.appendChild(textDiv); const colorDiv = document.createElement('div'); colorDiv.innerHTML = `<strong>Color:</strong> ${buttonConfig.backgroundColor}`; configDiv.appendChild(colorDiv); const conditionDiv = document.createElement('div'); conditionDiv.innerHTML = `<strong>Condition:</strong> ${buttonConfig.condition}`; configDiv.appendChild(conditionDiv); const messageDiv = document.createElement('div'); messageDiv.className = 'button-config-message'; messageDiv.innerText = buttonConfig.text; configDiv.appendChild(messageDiv); const editButton = document.createElement('button'); editButton.textContent = 'Edit'; editButton.addEventListener('click', () => { selectForEdit(index); switchTab('config-edit-tab'); }); configDiv.appendChild(editButton); const deleteButton = document.createElement('button'); deleteButton.textContent = 'Delete'; deleteButton.addEventListener('click', () => deleteButtonConfig(index)); configDiv.appendChild(deleteButton); const moveUpButton = document.createElement('button'); moveUpButton.textContent = 'Up'; moveUpButton.addEventListener('click', () => moveButtonConfig(index, -1)); configDiv.appendChild(moveUpButton); const moveDownButton = document.createElement('button'); moveDownButton.textContent = 'Down'; moveDownButton.addEventListener('click', () => moveButtonConfig(index, 1)); configDiv.appendChild(moveDownButton); configsContainer.appendChild(configDiv); }); } function selectForEdit(index) { const config = getButtonConfigurations().buttons[index]; document.getElementById('button-text').value = config.buttonText; document.getElementById('button-color').value = config.backgroundColor; document.getElementById('button-condition').value = config.condition; document.getElementById('button-text-content').value = config.text; document.getElementById('add-button').style.display = 'block'; document.getElementById('edit-button').style.display = 'block'; document.getElementById('edit-button').setAttribute('data-edit-index', index); updateCharCounter(); } function deleteButtonConfig(index) { const config = getButtonConfigurations(); config.buttons.splice(index, 1); saveButtonConfigurations(config); populateButtonConfigs(); showNotification('Button deleted.'); } function moveButtonConfig(index, direction) { const config = getButtonConfigurations(); const newIndex = index + direction; if (newIndex >= 0 && newIndex < config.buttons.length) { const buttonConfig = config.buttons.splice(index, 1)[0]; config.buttons.splice(newIndex, 0, buttonConfig); saveButtonConfigurations(config); populateButtonConfigs(); showNotification('Button moved.'); } } function addNewButtonConfig() { const buttonText = document.getElementById('button-text').value; const backgroundColor = document.getElementById('button-color').value; const condition = document.getElementById('button-condition').value; const text = document.getElementById('button-text-content').value; const config = getButtonConfigurations(); config.buttons.push({ buttonText, backgroundColor, condition, text }); saveButtonConfigurations(config); populateButtonConfigs(); highlightButton(config.buttons.length - 1); switchTab('config-list-tab'); clearInputFields(); showNotification('Button added.'); } function editButtonConfig() { const index = parseInt(document.getElementById('edit-button').getAttribute('data-edit-index'), 10); const buttonText = document.getElementById('button-text').value; const backgroundColor = document.getElementById('button-color').value; const condition = document.getElementById('button-condition').value; const text = document.getElementById('button-text-content').value; const config = getButtonConfigurations(); config.buttons[index] = { buttonText, backgroundColor, condition, text }; saveButtonConfigurations(config); populateButtonConfigs(); highlightButton(index); switchTab('config-list-tab'); document.getElementById('add-button').style.display = 'block'; document.getElementById('edit-button').style.display = 'none'; clearInputFields(); showNotification('Button edited.'); } function clearInputFields() { document.getElementById('button-text').value = ''; document.getElementById('button-text-content').value = ''; document.getElementById('button-color').value = ''; updateCharCounter(); } function closeUI() { const panel = document.querySelector('.custom-ui-panel'); if (panel) { panel.remove(); } } function createConfigButton() { const peopleButton = document.querySelector('#people_panel_button'); if (!peopleButton || document.querySelector('#chat-config-button')) return; const button = document.createElement('button'); button.id = 'chat-config-button'; button.type = 'button'; button.title = 'Edit Chat Buttons'; button.className = 'root___WHFbh root___J_YsG'; const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); svg.setAttribute('xmlns', 'http://www.w3.org/2000/svg'); svg.setAttribute('viewBox', '0 0 512 512'); svg.setAttribute('height', '24'); svg.setAttribute('width', '24'); svg.classList.add('root___DYylw', 'icon___M_Izz'); svg.innerHTML = ` <path d="M312 201.8c0-17.4 9.2-33.2 19.9-47C344.5 138.5 352 118.1 352 96c0-53-43-96-96-96s-96 43-96 96c0 22.1 7.5 42.5 20.1 58.8c10.7 13.8 19.9 29.6 19.9 47c0 29.9-24.3 54.2-54.2 54.2L112 256C50.1 256 0 306.1 0 368c0 20.9 13.4 38.7 32 45.3L32 464c0 26.5 21.5 48 48 48l352 0c26.5 0 48-21.5 48-48l0-50.7c18.6-6.6 32-24.4 32-45.3c0-61.9-50.1-112-112-112l-33.8 0c-29.9 0-54.2-24.3-54.2-54.2zM416 416l0 32L96 448l0-32 320 0z" fill="url(#config-default-blue)"/> <defs> <linearGradient id="config-default-blue" x1="0.5" x2="0.5" y2="1"> <stop offset="0" stop-color="#8faeb4"/> <stop offset="1" stop-color="#638c94"/> </linearGradient> <linearGradient id="config-hover-blue" x1="0.5" x2="0.5" y2="1"> <stop offset="0" stop-color="#eaf0f1"/> <stop offset="1" stop-color="#7b9fa6"/> </linearGradient> </defs> `; button.appendChild(svg); button.addEventListener('click', createUIPanel); const path = svg.querySelector('path'); button.addEventListener('mouseenter', () => path.setAttribute('fill', 'url(#config-hover-blue)')); button.addEventListener('mouseleave', () => path.setAttribute('fill', 'url(#config-default-blue)')); peopleButton.insertAdjacentElement('afterend', button); } function applyButtonConfigurations() { const configs = getButtonConfigurations(); // Query for both old and new chat window classes document.querySelectorAll('.root___FmdS_, .chat-box___mHm01').forEach(chatBox => { configs.buttons.forEach(buttonConfig => { const conditionFunc = conditions[buttonConfig.condition]; if (conditionFunc && conditionFunc(chatBox) && !chatBox.querySelector(`[data-button-text="${buttonConfig.buttonText}"]`)) { const button = document.createElement('button'); button.className = 'custom-chat-button'; button.innerText = buttonConfig.buttonText; button.style.backgroundColor = buttonConfig.backgroundColor; button.setAttribute('data-button-text', buttonConfig.buttonText); button.addEventListener('click', (event) => addCustomText(chatBox, buttonConfig.text, event)); button.addEventListener('mousedown', (event) => { if (event.button === 0) { let timer; const delay = 1000; timer = setTimeout(() => { button.classList.remove('recent'); clearRecentButtonInfo(); }, delay); button.addEventListener('mouseup', () => { clearTimeout(timer); }, { once: true }); button.addEventListener('mouseleave', () => { clearTimeout(timer); }, { once: true }); } }); // Try both new and old input container selectors const inputContainer = chatBox.querySelector('.root___WUd1h') || chatBox.querySelector('.chat-box-footer___YK914'); if (inputContainer) { let buttonContainer = chatBox.querySelector('.button-container'); if (!buttonContainer) { buttonContainer = document.createElement('div'); buttonContainer.className = 'button-container'; buttonContainer.style.display = 'flex'; buttonContainer.style.flexWrap = 'wrap'; inputContainer.insertAdjacentElement('beforebegin', buttonContainer); } buttonContainer.appendChild(button); } } }); }); } async function addCustomText(chatBox, messageTemplate, event) { let name = 'User'; // Try both new and old name selectors const titleElement = chatBox.querySelector('.title___Bmq5P') || chatBox.querySelector('.typography___Dc5WV') || chatBox.querySelector('.chat-box-header__name___jIjjM'); if (titleElement) { name = titleElement.textContent.trim(); } let message = messageTemplate.replace('{name}', name); if (message.includes('{company}')) { const apiKey = getAPIKey(); if (!apiKey) { alert('API key not set. Please set the API key in the settings tab.'); return; } let companyInfo = loadCache('companyCache'); if (!companyInfo) { const apiUrl = `https://api.torn.com/company/?selections=profile&key=${apiKey}`; try { const response = await fetch(apiUrl); const data = await response.json(); if (!data.error && data.company) { companyInfo = data.company; saveCache('companyCache', companyInfo); } else { alert('Failed to retrieve company information. Check your API key.'); return; } } catch (error) { alert('Error fetching company information:', error); return; } } const companyType = companyTypes[companyInfo.company_type] || 'Unknown'; const companyDetails = `${companyInfo.rating}* ${companyType}`; message = message.replace('{company}', companyDetails); } if (message.includes('{faction}')) { const apiKey = getAPIKey(); if (!apiKey) { alert('API key not set. Please set the API key in the settings tab.'); return; } let factionInfo = loadCache('factionCache'); if (!factionInfo) { const apiUrl = `https://api.torn.com/faction/?selections=basic&key=${apiKey}`; try { const response = await fetch(apiUrl); const data = await response.json(); if (!data.error && data.respect && data.name && data.rank) { factionInfo = data; saveCache('factionCache', factionInfo); } else { alert('Failed to retrieve faction information. Check your API key.'); return; } } catch (error) { alert('Error fetching faction information:', error); return; } } const respectFormatted = factionInfo.respect >= 1000000 ? (factionInfo.respect / 1000000).toFixed(1) + 'm' : (factionInfo.respect / 1000).toFixed(1) + 'k'; const factionDetails = `${factionInfo.name}, ${factionInfo.rank.name} Ranked ${respectFormatted} Respect`; message = message.replace('{faction}', factionDetails); } insertMessage(chatBox, message, event.target); } function insertMessage(chatBox, message, targetButton) { navigator.clipboard.writeText(message).then(() => { // Try both new and old textarea selectors const textArea = chatBox.querySelector('.textarea___V8HsV') || chatBox.querySelector('textarea'); if (!textArea) return; textArea.focus(); const startPos = textArea.selectionStart; const endPos = textArea.selectionEnd; textArea.setRangeText(message, startPos, endPos, 'end'); textArea.dispatchEvent(new Event('input', { bubbles: true })); textArea.focus(); textArea.selectionStart = textArea.selectionEnd = startPos + message.length; document.querySelectorAll('.custom-chat-button').forEach(btn => { btn.classList.remove('recent'); }); targetButton.classList.add('recent'); // Try both new and old title selectors const titleElement = chatBox.querySelector('.title___Bmq5P') || chatBox.querySelector('.chat-box-header__name___jIjjM'); const chatBoxName = titleElement ? titleElement.textContent.trim() : ''; saveRecentButtonInfo(targetButton.getAttribute('data-button-text'), chatBoxName); }); } function applyRecentButtonClass() { const recentButtonInfo = JSON.parse(localStorage.getItem('recentButtonInfo')); if (recentButtonInfo) { document.querySelectorAll('.custom-chat-button').forEach(btn => { btn.classList.remove('recent'); }); // Query for both old and new chat window classes document.querySelectorAll('.root___FmdS_, .chat-box___mHm01').forEach(chatBox => { // Try both new and old title selectors const titleElement = chatBox.querySelector('.title___Bmq5P') || chatBox.querySelector('.chat-box-header__name___jIjjM'); const chatBoxName = titleElement ? titleElement.textContent.trim() : ''; if (chatBoxName === recentButtonInfo.chatBoxName) { const button = chatBox.querySelector(`[data-button-text="${recentButtonInfo.buttonText}"]`); if (button) { button.classList.add('recent'); } } }); } } function importConfig() { const input = document.createElement('input'); input.type = 'file'; input.accept = '.json'; input.onchange = async (event) => { const file = event.target.files[0]; if (!file) { showNotification('No file selected.'); return; } const reader = new FileReader(); reader.onload = (e) => { try { const config = JSON.parse(e.target.result); if (config && config.buttons) { saveButtonConfigurations(config); populateButtonConfigs(); applyButtonConfigurations(); showNotification('Configuration imported from file.'); } else { showNotification('Invalid configuration file.'); } } catch (err) { showNotification('Error: Invalid JSON.'); } }; reader.readAsText(file); }; input.click(); } function exportConfig() { const config = getButtonConfigurations(); const blob = new Blob([JSON.stringify(config, null, 2)], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'chatButtonConfig.json'; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); showNotification('Configuration exported to file.'); } function updateCharCounter() { const textArea = document.getElementById('button-text-content'); if (!textArea) return; const counter = document.getElementById('char-counter'); if (!counter) return; counter.textContent = textArea.value.length; } function highlightButton(index) { const configsContainer = document.getElementById('button-configs'); const buttonDiv = configsContainer.querySelector(`.draggable[data-index="${index}"]`); if (buttonDiv) { buttonDiv.classList.add('highlight'); buttonDiv.scrollIntoView({ behavior: 'smooth', block: 'center' }); setTimeout(() => { buttonDiv.classList.remove('highlight'); }, 2000); } } addCSS(buttonCSS); const chatContainerObserver = new MutationObserver(function() { createConfigButton(); applyButtonConfigurations(); applyRecentButtonClass(); }); const chatContainer = document.querySelector('#chatRoot'); if (chatContainer) { chatContainerObserver.observe(chatContainer, { childList: true, subtree: true }); } applyButtonConfigurations(); applyRecentButtonClass(); })();