Torn.com Enhanced Chat Buttons V2

Add customizable buttons to Torn.com chat with enhanced UI and features

目前為 2025-05-05 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name Torn.com Enhanced Chat Buttons V2
  3. // @namespace http://tampermonkey.net/
  4. // @version 2.50
  5. // @description Add customizable buttons to Torn.com chat with enhanced UI and features
  6. // @author Created by Callz [2188704], updated by Weav3r [1853324]
  7. // @match https://www.torn.com/*
  8. // @grant GM_setClipboard
  9. // ==/UserScript==
  10.  
  11. (function() {
  12. 'use strict';
  13.  
  14. const CACHE_TTL = 24 * 60 * 60 * 1000;
  15. const BAZAAR_CACHE_TTL = 10 * 60 * 1000; // 10 minutes
  16.  
  17. const buttonCSS = `
  18. .custom-chat-button {
  19. background-color: #007BFF;
  20. color: white;
  21. padding: 2px 7px;
  22. text-align: center;
  23. text-decoration: none;
  24. display: inline-block;
  25. font-size: 14px;
  26. margin: 4px 6px;
  27. cursor: pointer;
  28. border-radius: 5px;
  29. border: none;
  30. transition: transform 0.1s ease, box-shadow 0.1s ease;
  31. min-width: 80px;
  32. overflow: hidden;
  33. white-space: nowrap;
  34. }
  35.  
  36. .custom-chat-button:active {
  37. transform: scale(0.95);
  38. box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.2);
  39. }
  40.  
  41. .custom-chat-button.recent {
  42. border: 2px solid #FFD700;
  43. box-shadow: 0 0 5px rgba(255, 215, 0, 0.8);
  44. }
  45.  
  46. .custom-ui-panel {
  47. position: fixed;
  48. top: 50%;
  49. left: 50%;
  50. transform: translate(-50%, -50%);
  51. background-color: #f5f5f5;
  52. padding: 10px;
  53. color: black;
  54. border-radius: 10px;
  55. box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
  56. z-index: 9999999999;
  57. width: 90%;
  58. max-width: 500px;
  59. box-sizing: border-box;
  60. max-height: 90vh;
  61. overflow: auto;
  62. }
  63.  
  64. .custom-ui-panel h3 {
  65. font-size: 20px;
  66. margin-bottom: 10px;
  67. text-align: center;
  68. }
  69.  
  70. .custom-ui-panel label {
  71. font-size: 14px;
  72. margin-bottom: 5px;
  73. display: block;
  74. }
  75.  
  76. .custom-ui-panel input[type="text"],
  77. .custom-ui-panel select,
  78. .custom-ui-panel textarea {
  79. width: calc(100% - 12px);
  80. padding: 5px;
  81. margin-bottom: 10px;
  82. border: 1px solid #ccc;
  83. border-radius: 5px;
  84. font-size: 14px;
  85. }
  86.  
  87. .custom-ui-panel input[type="color"] {
  88. padding: 0;
  89. margin-top: 5px;
  90. border: none;
  91. }
  92.  
  93. .custom-ui-panel button {
  94. background-color: #007BFF;
  95. color: white;
  96. border: none;
  97. padding: 8px 12px;
  98. border-radius: 5px;
  99. cursor: pointer;
  100. margin: 5px;
  101. font-size: 14px;
  102. transition: background-color 0.3s ease;
  103. }
  104.  
  105. .custom-ui-panel button#close-ui {
  106. background-color: #ccc;
  107. }
  108.  
  109. .custom-ui-panel button#close-ui:hover {
  110. background-color: #999;
  111. }
  112.  
  113. .custom-ui-panel textarea {
  114. height: 60px;
  115. resize: vertical;
  116. position: relative;
  117. }
  118.  
  119. .custom-ui-panel hr {
  120. margin: 10px 0;
  121. border: 0;
  122. border-top: 1px solid #ccc;
  123. }
  124.  
  125. .char-counter {
  126. position: absolute;
  127. bottom: 10px;
  128. right: 10px;
  129. font-size: 12px;
  130. color: #999;
  131. }
  132.  
  133. #chat-config-button {
  134. color: green;
  135. }
  136.  
  137. #button-configs {
  138. max-height: 400px;
  139. overflow-y: auto;
  140. margin-bottom: 10px;
  141. }
  142.  
  143. @media (max-width: 600px) {
  144. .custom-ui-panel {
  145. width: 95%;
  146. max-width: none;
  147. padding: 8px;
  148. }
  149.  
  150. .custom-ui-panel h3 {
  151. font-size: 18px;
  152. }
  153.  
  154. .custom-ui-panel label,
  155. .custom-ui-panel button {
  156. font-size: 12px;
  157. }
  158.  
  159. .custom-ui-panel input[type="text"],
  160. .custom-ui-panel select,
  161. .custom-ui-panel textarea {
  162. font-size: 12px;
  163. }
  164.  
  165. .custom-ui-panel button {
  166. padding: 6px 10px;
  167. }
  168.  
  169. .char-counter {
  170. font-size: 10px;
  171. }
  172. }
  173.  
  174. .tabs {
  175. display: flex;
  176. margin-bottom: 10px;
  177. }
  178.  
  179. .tab {
  180. flex: 1;
  181. padding: 10px;
  182. cursor: pointer;
  183. text-align: center;
  184. background-color: #e9e9e9;
  185. border: 1px solid #ccc;
  186. border-bottom: none;
  187. border-radius: 10px 10px 0 0;
  188. }
  189.  
  190. .tab.settings-tab {
  191. flex: 0.2;
  192. padding: 10px;
  193. cursor: pointer;
  194. text-align: center;
  195. background-color: #e9e9e9;
  196. border: 1px solid #ccc;
  197. border-bottom: none;
  198. border-radius: 10px 10px 0 0;
  199. }
  200.  
  201. .tab.active {
  202. background-color: #fff;
  203. border-bottom: 1px solid #fff;
  204. }
  205.  
  206. .tab-content {
  207. display: none;
  208. }
  209.  
  210. .tab-content.active {
  211. display: block;
  212. }
  213.  
  214. .custom-ui-panel.config-list-tab-active {
  215. max-height: 80vh;
  216. }
  217.  
  218. .search-container {
  219. display: flex;
  220. margin-bottom: 10px;
  221. }
  222.  
  223. .search-container input[type="text"] {
  224. flex: 3;
  225. padding: 5px;
  226. margin-right: 5px;
  227. }
  228.  
  229. .search-container select {
  230. flex: 1;
  231. padding: 5px;
  232. }
  233.  
  234. .highlight {
  235. background-color: yellow;
  236. }
  237.  
  238. .tt-chat-filter {
  239. display: flex;
  240. padding: 4px;
  241. align-items: center;
  242. background-color: var(--chat-box-bg);
  243. color: var(--chat-box-label-info);
  244. border-bottom: 1px solid var(--chat-box-input-border);
  245. margin-bottom: 8px;
  246. }
  247.  
  248. .tt-chat-filter input {
  249. margin-left: 4px;
  250. margin-right: 4px;
  251. border-radius: 5px;
  252. width: -webkit-fill-available;
  253. width: -moz-available;
  254. border: 1px solid var(--chat-box-input-border);
  255. background-color: var(--chat-box-bg);
  256. color: var(--chat-box-label-info);
  257. }
  258.  
  259. .notification {
  260. position: fixed;
  261. bottom: 20px;
  262. right: 20px;
  263. background-color: rgba(0, 0, 0, 0.8);
  264. color: white;
  265. padding: 10px;
  266. border-radius: 5px;
  267. z-index: 9999999999;
  268. opacity: 0;
  269. transition: opacity 0.5s ease;
  270. }
  271.  
  272. .notification.show {
  273. opacity: 1;
  274. }
  275.  
  276. .button-config-card {
  277. border: 1px solid #ccc;
  278. background-color: #fff;
  279. padding: 10px;
  280. margin: 10px 0;
  281. border-radius: 5px;
  282. }
  283.  
  284. .button-config-message {
  285. white-space: pre-wrap;
  286. background: #f9f9f9;
  287. padding: 5px;
  288. border-radius: 5px;
  289. border: 1px solid #ddd;
  290. margin: 5px 0;
  291. }
  292. `;
  293.  
  294. const conditions = {
  295. TradeChat: chatBox => {
  296. // New chat version
  297. if (chatBox.id === 'public_trade') return true;
  298. // Old chat version
  299. const oldTitle = chatBox.querySelector('.chat-box-header__name___jIjjM');
  300. return oldTitle && oldTitle.textContent === 'Trade';
  301. },
  302. HospitalChat: chatBox => {
  303. // New chat version
  304. if (chatBox.id === 'public_hospital') return true;
  305. // Old chat version
  306. const oldTitle = chatBox.querySelector('.chat-box-header__name___jIjjM');
  307. return oldTitle && oldTitle.textContent === 'Hospital';
  308. },
  309. FactionChat: chatBox => {
  310. // New chat version
  311. if (chatBox.id && chatBox.id.startsWith('faction-')) return true;
  312. // Old chat version
  313. const oldTitle = chatBox.querySelector('.chat-box-header__name___jIjjM');
  314. return oldTitle && oldTitle.textContent === 'Faction';
  315. },
  316. CompanyChat: chatBox => {
  317. // New chat version
  318. if (chatBox.id && chatBox.id.startsWith('company-')) return true;
  319. // Old chat version
  320. const oldTitle = chatBox.querySelector('.chat-box-header__name___jIjjM');
  321. return oldTitle && oldTitle.textContent === 'Company';
  322. },
  323. GlobalChat: chatBox => {
  324. // New chat version
  325. if (chatBox.id === 'public_global') return true;
  326. // Old chat version
  327. const oldTitle = chatBox.querySelector('.chat-box-header__name___jIjjM');
  328. return oldTitle && oldTitle.textContent === 'Global';
  329. },
  330. UserChat: chatBox => {
  331. // New chat version
  332. if (chatBox.id && chatBox.id.startsWith('private-')) return true;
  333. // Old chat version
  334. return chatBox.querySelector('.chat-box-header__options___nTsMU') !== null;
  335. }
  336. };
  337.  
  338. const companyTypes = {
  339. 1: "Hair Salon",
  340. 2: "Law Firm",
  341. 3: "Flower Shop",
  342. 4: "Car Dealership",
  343. 5: "Clothing Store",
  344. 6: "Gun Shop",
  345. 7: "Game Shop",
  346. 8: "Candle Shop",
  347. 9: "Toy Shop",
  348. 10: "Adult Novelties",
  349. 11: "Cyber Cafe",
  350. 12: "Grocery Store",
  351. 13: "Theater",
  352. 14: "Sweet Shop",
  353. 15: "Cruise Line",
  354. 16: "Television Network",
  355. 18: "Zoo",
  356. 19: "Firework Stand",
  357. 20: "Property Broker",
  358. 21: "Furniture Store",
  359. 22: "Gas Station",
  360. 23: "Music Store",
  361. 24: "Nightclub",
  362. 25: "Pub",
  363. 26: "Gents Strip Club",
  364. 27: "Restaurant",
  365. 28: "Oil Rig",
  366. 29: "Fitness Center",
  367. 30: "Mechanic Shop",
  368. 31: "Amusement Park",
  369. 32: "Lingerie Store",
  370. 33: "Meat Warehouse",
  371. 34: "Farm",
  372. 35: "Software Corporation",
  373. 36: "Ladies Strip Club",
  374. 37: "Private Security Firm",
  375. 38: "Mining Corporation",
  376. 39: "Detective Agency",
  377. 40: "Logistics Management",
  378. };
  379.  
  380. function addCSS(cssString) {
  381. const style = document.createElement('style');
  382. style.textContent = cssString;
  383. document.head.append(style);
  384. }
  385.  
  386. function showNotification(message) {
  387. const notification = document.createElement('div');
  388. notification.className = 'notification';
  389. notification.innerText = message;
  390. document.body.appendChild(notification);
  391. requestAnimationFrame(() => {
  392. notification.classList.add('show');
  393. });
  394. setTimeout(() => {
  395. notification.classList.remove('show');
  396. setTimeout(() => {
  397. notification.remove();
  398. }, 500);
  399. }, 2000);
  400. }
  401.  
  402. function saveRecentButtonInfo(buttonText, chatBoxName) {
  403. localStorage.setItem('recentButtonInfo', JSON.stringify({ buttonText, chatBoxName }));
  404. }
  405.  
  406. function clearRecentButtonInfo() {
  407. localStorage.removeItem('recentButtonInfo');
  408. }
  409.  
  410. function getButtonConfigurations() {
  411. return JSON.parse(localStorage.getItem('chatButtonConfig')) || { buttons: [] };
  412. }
  413.  
  414. function saveButtonConfigurations(config) {
  415. localStorage.setItem('chatButtonConfig', JSON.stringify(config));
  416. }
  417.  
  418. function getAPIKey() {
  419. return localStorage.getItem('apiKey') || '';
  420. }
  421.  
  422. function saveAPIKey(key) {
  423. localStorage.setItem('apiKey', key);
  424. showNotification('API key saved.');
  425. }
  426.  
  427. function saveCache(key, data) {
  428. const cacheData = {
  429. timestamp: Date.now(),
  430. data
  431. };
  432. localStorage.setItem(key, JSON.stringify(cacheData));
  433. }
  434.  
  435. function loadCache(key) {
  436. const cacheData = JSON.parse(localStorage.getItem(key));
  437. if (cacheData && (Date.now() - cacheData.timestamp < CACHE_TTL)) {
  438. return cacheData.data;
  439. }
  440. return null;
  441. }
  442.  
  443. function clearCache() {
  444. localStorage.removeItem('companyCache');
  445. localStorage.removeItem('factionCache');
  446. showNotification('API cache cleared.');
  447. }
  448.  
  449. function getBazaarData() {
  450. return loadCache('bazaarCache');
  451. }
  452.  
  453. function saveBazaarData(data) {
  454. const cacheData = {
  455. timestamp: Date.now(),
  456. data
  457. };
  458. localStorage.setItem('bazaarCache', JSON.stringify(cacheData));
  459. }
  460.  
  461. function loadBazaarCache() {
  462. const cacheData = JSON.parse(localStorage.getItem('bazaarCache'));
  463. if (cacheData && (Date.now() - cacheData.timestamp < BAZAAR_CACHE_TTL)) {
  464. return cacheData.data;
  465. }
  466. return null;
  467. }
  468.  
  469. function formatBazaarItems(bazaarData, maxLength = 125) {
  470. if (!bazaarData || !bazaarData.bazaar) return 'No bazaar data available';
  471.  
  472. const items = bazaarData.bazaar;
  473. const totalValue = items.reduce((sum, item) => sum + (item.price * item.quantity), 0);
  474. const groupedItems = {};
  475. items.forEach(item => {
  476. let type = item.type;
  477. let name = item.name;
  478. let displayName;
  479. if (name === 'Donator Pack') {
  480. displayName = 'DPs';
  481. } else if (name === 'Erotic DVD') {
  482. displayName = 'eDVD';
  483. } else if (name === 'Xanax') {
  484. displayName = 'Xan';
  485. } else if (name === 'Feathery Hotel Coupon') {
  486. displayName = 'FHC';
  487. } else if (['Heavy Arms Cache', 'Armor Cache', 'Melee Cache', 'Small Arms Cache', 'Medium Arms Cache'].includes(name)) {
  488. displayName = 'RW cache';
  489. } else if (name === 'Six-Pack of Energy Drink') {
  490. displayName = '6pack edrinks';
  491. } else if (name === 'Six-Pack of Alcohol') {
  492. displayName = '6pack alcohol';
  493. } else {
  494. if (type === 'Flower' || type === 'Plushie') {
  495. displayName = 'Flushies';
  496. } else if (['Melee', 'Primary', 'Secondary'].includes(type)) {
  497. displayName = 'Weapons';
  498. } else if (type === 'Defensive') {
  499. displayName = 'Armor';
  500. } else if (type === 'Energy Drink') {
  501. displayName = 'edrinks';
  502. } else {
  503. displayName = type;
  504. }
  505. }
  506.  
  507. if (!groupedItems[displayName]) {
  508. groupedItems[displayName] = { value: 0 };
  509. }
  510. groupedItems[displayName].value += item.price * item.quantity;
  511. });
  512.  
  513. const formattedItems = Object.entries(groupedItems)
  514. .map(([name, data]) => ({
  515. name,
  516. value: data.value,
  517. percentage: (data.value / totalValue) * 100,
  518. isWeaponOrArmor: name === 'Weapons' || name === 'Armor'
  519. }))
  520. .sort((a, b) => {
  521. if (a.isWeaponOrArmor !== b.isWeaponOrArmor) {
  522. return a.isWeaponOrArmor ? 1 : -1;
  523. }
  524. return b.percentage - a.percentage;
  525. });
  526.  
  527. const significantItems = formattedItems.filter(item => item.percentage >= 1);
  528. let currentLength = 0;
  529. let itemsToInclude = [];
  530. for (const item of significantItems) {
  531. const newLength = currentLength + (currentLength > 0 ? 2 : 0) + item.name.length;
  532. if (newLength <= maxLength) {
  533. itemsToInclude.push(item.name);
  534. currentLength = newLength;
  535. } else {
  536. break;
  537. }
  538. }
  539.  
  540. if (itemsToInclude.length === 0) return 'Empty bazaar';
  541. return itemsToInclude.join(', ');
  542. }
  543.  
  544. function createUIPanel() {
  545. if (document.querySelector('.custom-ui-panel')) {
  546. return;
  547. }
  548.  
  549. const panel = document.createElement('div');
  550. panel.className = 'custom-ui-panel';
  551. panel.innerHTML = `
  552. <div class="tabs">
  553. <div class="tab active" data-tab="config-list-tab">Configured Buttons</div>
  554. <div class="tab" data-tab="config-edit-tab">Create/Edit Button</div>
  555. <div class="tab settings-tab" data-tab="config-settings-tab">⚙️</div>
  556. </div>
  557. <div id="config-list-tab" class="tab-content active">
  558. <div class="search-container">
  559. <input type="text" id="search-input" placeholder="Search...">
  560. <select id="search-select">
  561. <option value="buttonText">Text</option>
  562. <option value="condition">Condition</option>
  563. <option value="text">Message</option>
  564. </select>
  565. </div>
  566. <div id="button-configs"></div>
  567. </div>
  568. <div id="config-edit-tab" class="tab-content">
  569. <div>
  570. <label for="button-text">Button Text</label>
  571. <input type="text" id="button-text" placeholder="Button Text">
  572.  
  573. <label for="button-color">Background Color</label>
  574. <input type="color" id="button-color">
  575.  
  576. <label for="button-condition">Condition</label>
  577. <select id="button-condition">
  578. <option value="TradeChat">Trade Chat</option>
  579. <option value="HospitalChat">Hospital Chat</option>
  580. <option value="FactionChat">Faction Chat</option>
  581. <option value="CompanyChat">Company Chat</option>
  582. <option value="GlobalChat">Global Chat</option>
  583. <option value="UserChat">User Chat</option>
  584. </select>
  585.  
  586. <label for="button-text-content">Message</label>
  587. <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>
  588. <div class="char-counter" id="char-counter">0</div>
  589.  
  590. <button id="add-button">Add Button</button>
  591. <button id="edit-button" style="display: none;">Save Button</button>
  592. </div>
  593. </div>
  594. <div id="config-settings-tab" class="tab-content">
  595. <label for="api-key">API Key</label>
  596. <input type="text" id="api-key" placeholder="Enter your API key" value="${getAPIKey()}">
  597. <button id="save-api-key-button">Save API Key</button>
  598. <button id="import-button">Import Config (File)</button>
  599. <button id="export-button">Export Config (File)</button>
  600. <button id="clear-cache-button">Clear API Cache</button>
  601. </div>
  602. <button id="close-ui">Close</button>
  603. `;
  604. document.body.appendChild(panel);
  605.  
  606. document.querySelectorAll('.tab').forEach(tab => {
  607. tab.addEventListener('click', () => {
  608. switchTab(tab.dataset.tab);
  609. });
  610. });
  611.  
  612. document.getElementById('add-button').addEventListener('click', addNewButtonConfig);
  613. document.getElementById('edit-button').addEventListener('click', editButtonConfig);
  614. document.getElementById('close-ui').addEventListener('click', closeUI);
  615. document.getElementById('import-button').addEventListener('click', importConfig);
  616. document.getElementById('export-button').addEventListener('click', exportConfig);
  617. document.getElementById('clear-cache-button').addEventListener('click', clearCache);
  618. document.getElementById('button-text-content').addEventListener('input', updateCharCounter);
  619. document.getElementById('search-input').addEventListener('input', filterButtonConfigs);
  620. document.getElementById('save-api-key-button').addEventListener('click', () => {
  621. const key = document.getElementById('api-key').value;
  622. saveAPIKey(key);
  623. });
  624.  
  625. populateButtonConfigs();
  626. }
  627.  
  628. function switchTab(tabId) {
  629. document.querySelectorAll('.tab, .tab-content').forEach(el => {
  630. el.classList.remove('active');
  631. });
  632. document.querySelector(`[data-tab="${tabId}"]`).classList.add('active');
  633. document.getElementById(tabId).classList.add('active');
  634.  
  635. const panel = document.querySelector('.custom-ui-panel');
  636. if (tabId === 'config-list-tab') {
  637. panel.classList.add('config-list-tab-active');
  638. } else {
  639. panel.classList.remove('config-list-tab-active');
  640. }
  641. }
  642.  
  643. function populateButtonConfigs() {
  644. const configsContainer = document.getElementById('button-configs');
  645. configsContainer.innerHTML = '';
  646. const configs = getButtonConfigurations();
  647.  
  648. configs.buttons.forEach((buttonConfig, index) => {
  649. const configDiv = document.createElement('div');
  650. configDiv.className = 'button-config-card draggable';
  651. configDiv.dataset.index = index;
  652.  
  653. const textDiv = document.createElement('div');
  654. textDiv.innerHTML = `<strong>Text:</strong> ${buttonConfig.buttonText}`;
  655. configDiv.appendChild(textDiv);
  656.  
  657. const colorDiv = document.createElement('div');
  658. colorDiv.innerHTML = `<strong>Color:</strong> ${buttonConfig.backgroundColor}`;
  659. configDiv.appendChild(colorDiv);
  660.  
  661. const conditionDiv = document.createElement('div');
  662. conditionDiv.innerHTML = `<strong>Condition:</strong> ${buttonConfig.condition}`;
  663. configDiv.appendChild(conditionDiv);
  664.  
  665. const messageDiv = document.createElement('div');
  666. messageDiv.className = 'button-config-message';
  667. messageDiv.innerText = buttonConfig.text;
  668. configDiv.appendChild(messageDiv);
  669.  
  670. const editButton = document.createElement('button');
  671. editButton.textContent = 'Edit';
  672. editButton.addEventListener('click', () => {
  673. selectForEdit(index);
  674. switchTab('config-edit-tab');
  675. });
  676. configDiv.appendChild(editButton);
  677.  
  678. const deleteButton = document.createElement('button');
  679. deleteButton.textContent = 'Delete';
  680. deleteButton.addEventListener('click', () => deleteButtonConfig(index));
  681. configDiv.appendChild(deleteButton);
  682.  
  683. const moveUpButton = document.createElement('button');
  684. moveUpButton.textContent = 'Up';
  685. moveUpButton.addEventListener('click', () => moveButtonConfig(index, -1));
  686. configDiv.appendChild(moveUpButton);
  687.  
  688. const moveDownButton = document.createElement('button');
  689. moveDownButton.textContent = 'Down';
  690. moveDownButton.addEventListener('click', () => moveButtonConfig(index, 1));
  691. configDiv.appendChild(moveDownButton);
  692.  
  693. configsContainer.appendChild(configDiv);
  694. });
  695. }
  696.  
  697. function filterButtonConfigs() {
  698. const searchInput = document.getElementById('search-input').value.toLowerCase();
  699. const searchBy = document.getElementById('search-select').value;
  700. const configs = getButtonConfigurations();
  701.  
  702. const filteredConfigs = configs.buttons.filter(buttonConfig => {
  703. const fieldValue = buttonConfig[searchBy].toLowerCase();
  704. return fieldValue.includes(searchInput);
  705. });
  706.  
  707. const configsContainer = document.getElementById('button-configs');
  708. configsContainer.innerHTML = '';
  709.  
  710. filteredConfigs.forEach((buttonConfig, index) => {
  711. const configDiv = document.createElement('div');
  712. configDiv.className = 'button-config-card draggable';
  713. configDiv.dataset.index = index;
  714.  
  715. const textDiv = document.createElement('div');
  716. textDiv.innerHTML = `<strong>Text:</strong> ${buttonConfig.buttonText}`;
  717. configDiv.appendChild(textDiv);
  718.  
  719. const colorDiv = document.createElement('div');
  720. colorDiv.innerHTML = `<strong>Color:</strong> ${buttonConfig.backgroundColor}`;
  721. configDiv.appendChild(colorDiv);
  722.  
  723. const conditionDiv = document.createElement('div');
  724. conditionDiv.innerHTML = `<strong>Condition:</strong> ${buttonConfig.condition}`;
  725. configDiv.appendChild(conditionDiv);
  726.  
  727. const messageDiv = document.createElement('div');
  728. messageDiv.className = 'button-config-message';
  729. messageDiv.innerText = buttonConfig.text;
  730. configDiv.appendChild(messageDiv);
  731.  
  732. const editButton = document.createElement('button');
  733. editButton.textContent = 'Edit';
  734. editButton.addEventListener('click', () => {
  735. selectForEdit(index);
  736. switchTab('config-edit-tab');
  737. });
  738. configDiv.appendChild(editButton);
  739.  
  740. const deleteButton = document.createElement('button');
  741. deleteButton.textContent = 'Delete';
  742. deleteButton.addEventListener('click', () => deleteButtonConfig(index));
  743. configDiv.appendChild(deleteButton);
  744.  
  745. const moveUpButton = document.createElement('button');
  746. moveUpButton.textContent = 'Up';
  747. moveUpButton.addEventListener('click', () => moveButtonConfig(index, -1));
  748. configDiv.appendChild(moveUpButton);
  749.  
  750. const moveDownButton = document.createElement('button');
  751. moveDownButton.textContent = 'Down';
  752. moveDownButton.addEventListener('click', () => moveButtonConfig(index, 1));
  753. configDiv.appendChild(moveDownButton);
  754.  
  755. configsContainer.appendChild(configDiv);
  756. });
  757. }
  758.  
  759. function selectForEdit(index) {
  760. const config = getButtonConfigurations().buttons[index];
  761. document.getElementById('button-text').value = config.buttonText;
  762. document.getElementById('button-color').value = config.backgroundColor;
  763. document.getElementById('button-condition').value = config.condition;
  764. document.getElementById('button-text-content').value = config.text;
  765.  
  766. document.getElementById('add-button').style.display = 'block';
  767. document.getElementById('edit-button').style.display = 'block';
  768. document.getElementById('edit-button').setAttribute('data-edit-index', index);
  769. updateCharCounter();
  770. }
  771.  
  772. function deleteButtonConfig(index) {
  773. const config = getButtonConfigurations();
  774. config.buttons.splice(index, 1);
  775. saveButtonConfigurations(config);
  776. populateButtonConfigs();
  777. showNotification('Button deleted.');
  778. }
  779.  
  780. function moveButtonConfig(index, direction) {
  781. const config = getButtonConfigurations();
  782. const newIndex = index + direction;
  783.  
  784. if (newIndex >= 0 && newIndex < config.buttons.length) {
  785. const buttonConfig = config.buttons.splice(index, 1)[0];
  786. config.buttons.splice(newIndex, 0, buttonConfig);
  787. saveButtonConfigurations(config);
  788. populateButtonConfigs();
  789. showNotification('Button moved.');
  790. }
  791. }
  792.  
  793. function addNewButtonConfig() {
  794. const buttonText = document.getElementById('button-text').value;
  795. const backgroundColor = document.getElementById('button-color').value;
  796. const condition = document.getElementById('button-condition').value;
  797. const text = document.getElementById('button-text-content').value;
  798.  
  799. const config = getButtonConfigurations();
  800. config.buttons.push({ buttonText, backgroundColor, condition, text });
  801. saveButtonConfigurations(config);
  802. populateButtonConfigs();
  803. highlightButton(config.buttons.length - 1);
  804. switchTab('config-list-tab');
  805.  
  806. clearInputFields();
  807. showNotification('Button added.');
  808. }
  809.  
  810. function editButtonConfig() {
  811. const index = parseInt(document.getElementById('edit-button').getAttribute('data-edit-index'), 10);
  812. const buttonText = document.getElementById('button-text').value;
  813. const backgroundColor = document.getElementById('button-color').value;
  814. const condition = document.getElementById('button-condition').value;
  815. const text = document.getElementById('button-text-content').value;
  816.  
  817. const config = getButtonConfigurations();
  818. config.buttons[index] = { buttonText, backgroundColor, condition, text };
  819. saveButtonConfigurations(config);
  820. populateButtonConfigs();
  821. highlightButton(index);
  822. switchTab('config-list-tab');
  823.  
  824. document.getElementById('add-button').style.display = 'block';
  825. document.getElementById('edit-button').style.display = 'none';
  826.  
  827. clearInputFields();
  828. showNotification('Button edited.');
  829. }
  830.  
  831. function clearInputFields() {
  832. document.getElementById('button-text').value = '';
  833. document.getElementById('button-text-content').value = '';
  834. document.getElementById('button-color').value = '';
  835. updateCharCounter();
  836. }
  837.  
  838. function closeUI() {
  839. const panel = document.querySelector('.custom-ui-panel');
  840. if (panel) {
  841. panel.remove();
  842. }
  843. }
  844.  
  845. function createConfigButton() {
  846. const peopleButton = document.querySelector('#people_panel_button');
  847. if (!peopleButton || document.querySelector('#chat-config-button')) return;
  848.  
  849. const button = document.createElement('button');
  850. button.id = 'chat-config-button';
  851. button.type = 'button';
  852. button.title = 'Edit Chat Buttons';
  853. button.className = 'root___WHFbh root___J_YsG';
  854.  
  855. const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
  856. svg.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
  857. svg.setAttribute('viewBox', '0 0 512 512');
  858. svg.setAttribute('height', '24');
  859. svg.setAttribute('width', '24');
  860. svg.classList.add('root___DYylw', 'icon___M_Izz');
  861. svg.innerHTML = `
  862. <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)"/>
  863. <defs>
  864. <linearGradient id="config-default-blue" x1="0.5" x2="0.5" y2="1">
  865. <stop offset="0" stop-color="#8faeb4"/>
  866. <stop offset="1" stop-color="#638c94"/>
  867. </linearGradient>
  868. <linearGradient id="config-hover-blue" x1="0.5" x2="0.5" y2="1">
  869. <stop offset="0" stop-color="#eaf0f1"/>
  870. <stop offset="1" stop-color="#7b9fa6"/>
  871. </linearGradient>
  872. </defs>
  873. `;
  874.  
  875. button.appendChild(svg);
  876. button.addEventListener('click', createUIPanel);
  877.  
  878. const path = svg.querySelector('path');
  879. button.addEventListener('mouseenter', () => path.setAttribute('fill', 'url(#config-hover-blue)'));
  880. button.addEventListener('mouseleave', () => path.setAttribute('fill', 'url(#config-default-blue)'));
  881.  
  882. peopleButton.insertAdjacentElement('afterend', button);
  883. }
  884.  
  885. function applyButtonConfigurations() {
  886. const configs = getButtonConfigurations();
  887. // Query for both old and new chat window classes
  888. document.querySelectorAll('.root___FmdS_, .chat-box___mHm01').forEach(chatBox => {
  889. configs.buttons.forEach(buttonConfig => {
  890. const conditionFunc = conditions[buttonConfig.condition];
  891. if (conditionFunc && conditionFunc(chatBox) && !chatBox.querySelector(`[data-button-text="${buttonConfig.buttonText}"]`)) {
  892. const button = document.createElement('button');
  893. button.className = 'custom-chat-button';
  894. button.innerText = buttonConfig.buttonText;
  895. button.style.backgroundColor = buttonConfig.backgroundColor;
  896. button.setAttribute('data-button-text', buttonConfig.buttonText);
  897. button.addEventListener('click', (event) => addCustomText(chatBox, buttonConfig.text, event));
  898. button.addEventListener('mousedown', (event) => {
  899. if (event.button === 0) {
  900. let timer;
  901. const delay = 1000;
  902. timer = setTimeout(() => {
  903. button.classList.remove('recent');
  904. clearRecentButtonInfo();
  905. }, delay);
  906. button.addEventListener('mouseup', () => {
  907. clearTimeout(timer);
  908. }, { once: true });
  909. button.addEventListener('mouseleave', () => {
  910. clearTimeout(timer);
  911. }, { once: true });
  912. }
  913. });
  914.  
  915. // Try both new and old input container selectors
  916. const inputContainer = chatBox.querySelector('.root___WUd1h') || chatBox.querySelector('.chat-box-footer___YK914');
  917. if (inputContainer) {
  918. let buttonContainer = chatBox.querySelector('.button-container');
  919. if (!buttonContainer) {
  920. buttonContainer = document.createElement('div');
  921. buttonContainer.className = 'button-container';
  922. buttonContainer.style.display = 'flex';
  923. buttonContainer.style.flexWrap = 'wrap';
  924. inputContainer.insertAdjacentElement('beforebegin', buttonContainer);
  925. }
  926. buttonContainer.appendChild(button);
  927. }
  928. }
  929. });
  930. });
  931. }
  932.  
  933. async function addCustomText(chatBox, messageTemplate, event) {
  934. let name = 'User';
  935. // Try both new and old name selectors
  936. const titleElement = chatBox.querySelector('.title___Bmq5P') ||
  937. chatBox.querySelector('.typography___Dc5WV') ||
  938. chatBox.querySelector('.chat-box-header__name___jIjjM');
  939. if (titleElement) {
  940. name = titleElement.textContent.trim();
  941. }
  942. let message = messageTemplate.replace('{name}', name);
  943.  
  944. if (message.includes('{bazaar}')) {
  945. const apiKey = getAPIKey();
  946. if (!apiKey) {
  947. alert('API key not set. Please set the API key in the settings tab.');
  948. return;
  949. }
  950.  
  951. let bazaarData = loadBazaarCache();
  952. if (!bazaarData) {
  953. const apiUrl = `https://api.torn.com/user/?key=${apiKey}&selections=bazaar`;
  954. try {
  955. const response = await fetch(apiUrl);
  956. const data = await response.json();
  957. if (!data.error) {
  958. bazaarData = data;
  959. saveBazaarData(bazaarData);
  960. } else {
  961. alert('Failed to retrieve bazaar information. Check your API key.');
  962. return;
  963. }
  964. } catch (error) {
  965. alert('Error fetching bazaar information:', error);
  966. return;
  967. }
  968. }
  969.  
  970. // Calculate available space for bazaar list
  971. const messageWithoutBazaar = message.replace('{bazaar}', '');
  972. const availableSpace = 125 - messageWithoutBazaar.length;
  973. const bazaarText = formatBazaarItems(bazaarData, availableSpace);
  974. message = message.replace('{bazaar}', bazaarText);
  975. }
  976.  
  977. if (message.includes('{company}')) {
  978. const apiKey = getAPIKey();
  979. if (!apiKey) {
  980. alert('API key not set. Please set the API key in the settings tab.');
  981. return;
  982. }
  983.  
  984. let companyInfo = loadCache('companyCache');
  985. if (!companyInfo) {
  986. const apiUrl = `https://api.torn.com/company/?selections=profile&key=${apiKey}`;
  987. try {
  988. const response = await fetch(apiUrl);
  989. const data = await response.json();
  990. if (!data.error && data.company) {
  991. companyInfo = data.company;
  992. saveCache('companyCache', companyInfo);
  993. } else {
  994. alert('Failed to retrieve company information. Check your API key.');
  995. return;
  996. }
  997. } catch (error) {
  998. alert('Error fetching company information:', error);
  999. return;
  1000. }
  1001. }
  1002.  
  1003. const companyType = companyTypes[companyInfo.company_type] || 'Unknown';
  1004. const companyDetails = `${companyInfo.rating}* ${companyType}`;
  1005. message = message.replace('{company}', companyDetails);
  1006. }
  1007.  
  1008. if (message.includes('{faction}')) {
  1009. const apiKey = getAPIKey();
  1010. if (!apiKey) {
  1011. alert('API key not set. Please set the API key in the settings tab.');
  1012. return;
  1013. }
  1014.  
  1015. let factionInfo = loadCache('factionCache');
  1016. if (!factionInfo) {
  1017. const apiUrl = `https://api.torn.com/faction/?selections=basic&key=${apiKey}`;
  1018. try {
  1019. const response = await fetch(apiUrl);
  1020. const data = await response.json();
  1021. if (!data.error && data.respect && data.name && data.rank) {
  1022. factionInfo = data;
  1023. saveCache('factionCache', factionInfo);
  1024. } else {
  1025. alert('Failed to retrieve faction information. Check your API key.');
  1026. return;
  1027. }
  1028. } catch (error) {
  1029. alert('Error fetching faction information:', error);
  1030. return;
  1031. }
  1032. }
  1033.  
  1034. const respectFormatted = factionInfo.respect >= 1000000 ? (factionInfo.respect / 1000000).toFixed(1) + 'm' : (factionInfo.respect / 1000).toFixed(1) + 'k';
  1035. const factionDetails = `${factionInfo.name}, ${factionInfo.rank.name} Ranked ${respectFormatted} Respect`;
  1036. message = message.replace('{faction}', factionDetails);
  1037. }
  1038.  
  1039. insertMessage(chatBox, message, event.target);
  1040. }
  1041.  
  1042. function insertMessage(chatBox, message, targetButton) {
  1043. navigator.clipboard.writeText(message).then(() => {
  1044. // Try both new and old textarea selectors
  1045. const textArea = chatBox.querySelector('.textarea___V8HsV') || chatBox.querySelector('textarea');
  1046. if (!textArea) return;
  1047. textArea.focus();
  1048. const startPos = textArea.selectionStart;
  1049. const endPos = textArea.selectionEnd;
  1050. textArea.setRangeText(message, startPos, endPos, 'end');
  1051. textArea.dispatchEvent(new Event('input', { bubbles: true }));
  1052. textArea.focus();
  1053. textArea.selectionStart = textArea.selectionEnd = startPos + message.length;
  1054.  
  1055. document.querySelectorAll('.custom-chat-button').forEach(btn => {
  1056. btn.classList.remove('recent');
  1057. });
  1058. targetButton.classList.add('recent');
  1059.  
  1060. // Try both new and old title selectors
  1061. const titleElement = chatBox.querySelector('.title___Bmq5P') ||
  1062. chatBox.querySelector('.chat-box-header__name___jIjjM');
  1063. const chatBoxName = titleElement ? titleElement.textContent.trim() : '';
  1064. saveRecentButtonInfo(targetButton.getAttribute('data-button-text'), chatBoxName);
  1065. });
  1066. }
  1067.  
  1068. function applyRecentButtonClass() {
  1069. const recentButtonInfo = JSON.parse(localStorage.getItem('recentButtonInfo'));
  1070. if (recentButtonInfo) {
  1071. document.querySelectorAll('.custom-chat-button').forEach(btn => {
  1072. btn.classList.remove('recent');
  1073. });
  1074.  
  1075. // Query for both old and new chat window classes
  1076. document.querySelectorAll('.root___FmdS_, .chat-box___mHm01').forEach(chatBox => {
  1077. // Try both new and old title selectors
  1078. const titleElement = chatBox.querySelector('.title___Bmq5P') ||
  1079. chatBox.querySelector('.chat-box-header__name___jIjjM');
  1080. const chatBoxName = titleElement ? titleElement.textContent.trim() : '';
  1081. if (chatBoxName === recentButtonInfo.chatBoxName) {
  1082. const button = chatBox.querySelector(`[data-button-text="${recentButtonInfo.buttonText}"]`);
  1083. if (button) {
  1084. button.classList.add('recent');
  1085. }
  1086. }
  1087. });
  1088. }
  1089. }
  1090.  
  1091. function importConfig() {
  1092. const input = document.createElement('input');
  1093. input.type = 'file';
  1094. input.accept = '.json';
  1095. input.onchange = async (event) => {
  1096. const file = event.target.files[0];
  1097. if (!file) {
  1098. showNotification('No file selected.');
  1099. return;
  1100. }
  1101. const reader = new FileReader();
  1102. reader.onload = (e) => {
  1103. try {
  1104. const config = JSON.parse(e.target.result);
  1105. if (config && config.buttons) {
  1106. saveButtonConfigurations(config);
  1107. populateButtonConfigs();
  1108. applyButtonConfigurations();
  1109. showNotification('Configuration imported from file.');
  1110. } else {
  1111. showNotification('Invalid configuration file.');
  1112. }
  1113. } catch (err) {
  1114. showNotification('Error: Invalid JSON.');
  1115. }
  1116. };
  1117. reader.readAsText(file);
  1118. };
  1119. input.click();
  1120. }
  1121.  
  1122. function exportConfig() {
  1123. const config = getButtonConfigurations();
  1124. const blob = new Blob([JSON.stringify(config, null, 2)], { type: 'application/json' });
  1125. const url = URL.createObjectURL(blob);
  1126. const a = document.createElement('a');
  1127. a.href = url;
  1128. a.download = 'chatButtonConfig.json';
  1129. document.body.appendChild(a);
  1130. a.click();
  1131. document.body.removeChild(a);
  1132. URL.revokeObjectURL(url);
  1133. showNotification('Configuration exported to file.');
  1134. }
  1135.  
  1136. function updateCharCounter() {
  1137. const textArea = document.getElementById('button-text-content');
  1138. if (!textArea) return;
  1139. const counter = document.getElementById('char-counter');
  1140. if (!counter) return;
  1141. counter.textContent = textArea.value.length;
  1142. }
  1143.  
  1144. function highlightButton(index) {
  1145. const configsContainer = document.getElementById('button-configs');
  1146. const buttonDiv = configsContainer.querySelector(`.draggable[data-index="${index}"]`);
  1147. if (buttonDiv) {
  1148. buttonDiv.classList.add('highlight');
  1149. buttonDiv.scrollIntoView({ behavior: 'smooth', block: 'center' });
  1150. setTimeout(() => {
  1151. buttonDiv.classList.remove('highlight');
  1152. }, 2000);
  1153. }
  1154. }
  1155.  
  1156. addCSS(buttonCSS);
  1157.  
  1158. const chatContainerObserver = new MutationObserver(function() {
  1159. createConfigButton();
  1160. applyButtonConfigurations();
  1161. applyRecentButtonClass();
  1162. });
  1163.  
  1164. const chatContainer = document.querySelector('#chatRoot');
  1165. if (chatContainer) {
  1166. chatContainerObserver.observe(chatContainer, { childList: true, subtree: true });
  1167. }
  1168.  
  1169. applyButtonConfigurations();
  1170. applyRecentButtonClass();
  1171. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址