您需要先安装一个扩展,例如 篡改猴、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.02 // @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 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; } .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); } `; const conditions = { TradeChat: chatBox => chatBox.querySelector('.chat-box-header__name___jIjjM').textContent === 'Trade', HospitalChat: chatBox => chatBox.querySelector('.chat-box-header__name___jIjjM').textContent === 'Hospital', FactionChat: chatBox => chatBox.querySelector('.chat-box-header__name___jIjjM').textContent === 'Faction', CompanyChat: chatBox => chatBox.querySelector('.chat-box-header__name___jIjjM').textContent === 'Company', GlobalChat: chatBox => chatBox.querySelector('.chat-box-header__name___jIjjM').textContent === 'Global', UserChat: chatBox => chatBox.querySelector('.chat-box-header__options___nTsMU'), }; function addCSS(cssString) { const style = document.createElement('style'); style.textContent = cssString; document.head.append(style); } 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 createUIPanel() { 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="Message content"></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"> <button id="import-button">Import Config</button> <button id="export-button">Export Config</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('button-text-content').addEventListener('input', updateCharCounter); document.getElementById('search-input').addEventListener('input', filterButtonConfigs); 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 = 'draggable'; configDiv.dataset.index = index; configDiv.innerHTML = ` <div><strong>Text:</strong> ${buttonConfig.buttonText}</div> <div><strong>Color:</strong> ${buttonConfig.backgroundColor}</div> <div><strong>Condition:</strong> ${buttonConfig.condition}</div> <div><strong>Message:</strong> ${buttonConfig.text}</div> `; 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 = 'draggable'; configDiv.dataset.index = index; configDiv.innerHTML = ` <div><strong>Text:</strong> ${buttonConfig.buttonText}</div> <div><strong>Color:</strong> ${buttonConfig.backgroundColor}</div> <div><strong>Condition:</strong> ${buttonConfig.condition}</div> <div><strong>Message:</strong> ${buttonConfig.text}</div> `; 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); } function deleteButtonConfig(index) { const config = getButtonConfigurations(); config.buttons.splice(index, 1); saveButtonConfigurations(config); populateButtonConfigs(); } 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(); } } 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(); } 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(); } function clearInputFields() { document.getElementById('button-text').value = ''; document.getElementById('button-text-content').value = ''; } function closeUI() { document.querySelector('.custom-ui-panel').remove(); } function createConfigButton() { const settingsPanel = document.querySelector('.settings-panel___IZSDs'); if (settingsPanel && !document.querySelector('#chat-config-button')) { const configButton = document.createElement('button'); configButton.id = 'chat-config-button'; configButton.textContent = 'Edit Chat Buttons'; configButton.addEventListener('click', createUIPanel); settingsPanel.appendChild(configButton); } } function applyButtonConfigurations() { const configs = getButtonConfigurations(); document.querySelectorAll('.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) { // Left mouse button let timer; const delay = 1000; // 1 second timer = setTimeout(() => { button.classList.remove('recent'); clearRecentButtonInfo(); }, delay); button.addEventListener('mouseup', () => { clearTimeout(timer); }, { once: true }); button.addEventListener('mouseleave', () => { clearTimeout(timer); }, { once: true }); } }); // Add button to the appropriate container const filterContainer = chatBox.querySelector('.tt-chat-filter'); const footerContainer = chatBox.querySelector('.chat-box-footer___YK914'); if (filterContainer) { filterContainer.insertBefore(button, filterContainer.firstChild); } else if (footerContainer) { const buttonContainer = chatBox.querySelector('.button-container') || document.createElement('div'); buttonContainer.className = 'button-container'; buttonContainer.style.display = 'flex'; buttonContainer.style.flexWrap = 'wrap'; footerContainer.insertAdjacentElement('beforebegin', buttonContainer); buttonContainer.appendChild(button); } else { const buttonContainer = document.createElement('div'); buttonContainer.className = 'button-container'; buttonContainer.style.display = 'flex'; buttonContainer.style.flexWrap = 'wrap'; chatBox.insertBefore(buttonContainer, chatBox.firstChild); buttonContainer.appendChild(button); } } }); }); } function addCustomText(chatBox, messageTemplate, event) { const nameElement = chatBox.querySelector('.typography___Dc5WV'); const name = nameElement ? nameElement.textContent.trim() : 'Trader'; const message = messageTemplate.replace('{name}', name); navigator.clipboard.writeText(message).then(() => { const textArea = chatBox.querySelector('textarea'); textArea.focus(); textArea.value = ''; // Clear the existing text 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; // Remove the 'recent' class from all buttons chatBox.querySelectorAll('.custom-chat-button').forEach(btn => { btn.classList.remove('recent'); }); // Add the 'recent' class to the clicked button event.target.classList.add('recent'); // Save the recently clicked button information const chatBoxName = chatBox.querySelector('.chat-box-header__name___jIjjM').textContent; saveRecentButtonInfo(event.target.getAttribute('data-button-text'), chatBoxName); }); } function applyRecentButtonClass() { const recentButtonInfo = JSON.parse(localStorage.getItem('recentButtonInfo')); if (recentButtonInfo) { document.querySelectorAll('.chat-box___mHm01').forEach(chatBox => { const chatBoxName = chatBox.querySelector('.chat-box-header__name___jIjjM').textContent; 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 = (event) => { const file = event.target.files[0]; const reader = new FileReader(); reader.onload = (e) => { const config = JSON.parse(e.target.result); saveButtonConfigurations(config); populateButtonConfigs(); applyButtonConfigurations(); }; 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); } function updateCharCounter() { const textArea = document.getElementById('button-text-content'); const counter = document.getElementById('char-counter'); 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(mutations) { mutations.forEach(function(mutation) { createConfigButton(); applyButtonConfigurations(); applyRecentButtonClass(); }); }); const chatContainer = document.querySelector('#chatRoot'); if (chatContainer) { chatContainerObserver.observe(chatContainer, { childList: true, subtree: true }); } applyButtonConfigurations(); applyRecentButtonClass(); })();