Torn.com Enhanced Chat Buttons V2

Add customizable buttons to your chats in Torn to make your life easier

  1. // ==UserScript==
  2. // @name Torn.com Enhanced Chat Buttons V2
  3. // @namespace http://tampermonkey.net/
  4. // @version 2.62
  5. // @description Add customizable buttons to your chats in Torn to make your life easier
  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 getBuyItems() {
  423. return localStorage.getItem('buyItems') || '';
  424. }
  425.  
  426. function saveBuyItems(items) {
  427. localStorage.setItem('buyItems', items);
  428. showNotification('Buy items saved.');
  429. }
  430.  
  431. function saveAPIKey(key) {
  432. localStorage.setItem('apiKey', key);
  433. showNotification('API key saved.');
  434. }
  435.  
  436. function saveCache(key, data) {
  437. const cacheData = {
  438. timestamp: Date.now(),
  439. data
  440. };
  441. localStorage.setItem(key, JSON.stringify(cacheData));
  442. }
  443.  
  444. function loadCache(key) {
  445. const cacheData = JSON.parse(localStorage.getItem(key));
  446. if (cacheData && (Date.now() - cacheData.timestamp < CACHE_TTL)) {
  447. return cacheData.data;
  448. }
  449. return null;
  450. }
  451.  
  452. function clearCache() {
  453. localStorage.removeItem('companyCache');
  454. localStorage.removeItem('factionCache');
  455. showNotification('API cache cleared.');
  456. }
  457.  
  458. function getBazaarData() {
  459. return loadCache('bazaarCache');
  460. }
  461.  
  462. function saveBazaarData(data) {
  463. const cacheData = {
  464. timestamp: Date.now(),
  465. data
  466. };
  467. localStorage.setItem('bazaarCache', JSON.stringify(cacheData));
  468. }
  469.  
  470. function loadBazaarCache() {
  471. const cacheData = JSON.parse(localStorage.getItem('bazaarCache'));
  472. if (cacheData && (Date.now() - cacheData.timestamp < BAZAAR_CACHE_TTL)) {
  473. return cacheData.data;
  474. }
  475. return null;
  476. }
  477.  
  478. function formatBazaarItems(bazaarData, maxLength = 125) {
  479. if (!bazaarData || !bazaarData.bazaar) return 'No bazaar data available';
  480.  
  481. const items = bazaarData.bazaar;
  482. const totalValue = items.reduce((sum, item) => sum + (item.price * item.quantity), 0);
  483.  
  484. const groupedItems = {};
  485. items.forEach(item => {
  486. let type = item.type;
  487. let name = item.name;
  488.  
  489. let displayName;
  490.  
  491. if (name === 'Donator Pack') {
  492. displayName = 'DPs';
  493. } else if (name === 'Erotic DVD') {
  494. displayName = 'eDVD';
  495. } else if (name === 'Xanax') {
  496. displayName = 'Xan';
  497. } else if (name === 'Feathery Hotel Coupon') {
  498. displayName = 'FHC';
  499. } else if (['Heavy Arms Cache', 'Armor Cache', 'Melee Cache', 'Small Arms Cache', 'Medium Arms Cache'].includes(name)) {
  500. displayName = 'RW cache';
  501. } else if (name === 'Six-Pack of Energy Drink') {
  502. displayName = '6pack edrinks';
  503. } else if (name === 'Six-Pack of Alcohol') {
  504. displayName = '6pack alcohol';
  505. } else {
  506. if (type === 'Flower' || type === 'Plushie') {
  507. displayName = 'Flushies';
  508. } else if (type === 'Energy Drink') {
  509. displayName = 'edrinks';
  510. } else if (['Melee', 'Primary', 'Secondary', 'Temporary', 'Clothing', 'Jewelry', 'Defensive', 'Special', 'Miscellaneous', 'Enhancer', 'Tools', 'Cars'].includes(type)) {
  511. displayName = 'Misc';
  512. } else {
  513. displayName = type;
  514. }
  515. }
  516.  
  517. if (!groupedItems[displayName]) {
  518. groupedItems[displayName] = { value: 0 };
  519. }
  520. groupedItems[displayName].value += item.price * item.quantity;
  521. });
  522.  
  523. const formattedItems = Object.entries(groupedItems)
  524. .map(([name, data]) => ({
  525. name,
  526. value: data.value,
  527. percentage: (data.value / totalValue) * 100,
  528. isMisc: name === 'Misc'
  529. }))
  530. .sort((a, b) => {
  531. if (a.isMisc && !b.isMisc) return 1;
  532. if (!a.isMisc && b.isMisc) return -1;
  533. return b.percentage - a.percentage;
  534. });
  535.  
  536. const significantItems = formattedItems.filter(item => item.percentage >= 1);
  537.  
  538. let currentLength = 0;
  539. let itemsToInclude = [];
  540.  
  541. for (const item of significantItems) {
  542. const newLength = currentLength + (currentLength > 0 ? 2 : 0) + item.name.length;
  543. if (newLength <= maxLength) {
  544. itemsToInclude.push(item.name);
  545. currentLength = newLength;
  546. } else {
  547. break;
  548. }
  549. }
  550.  
  551. if (itemsToInclude.length === 0) return 'Empty bazaar';
  552.  
  553. return itemsToInclude.join(', ');
  554. }
  555.  
  556. function formatBuyItems(maxLength = 125) {
  557. const buyItems = getBuyItems().split(',').map(item => item.trim()).filter(item => item);
  558. if (buyItems.length === 0) return 'No buy items set';
  559.  
  560. // Get the last used index from localStorage
  561. let lastIndex = parseInt(localStorage.getItem('lastBuyItemIndex') || '0');
  562.  
  563. let currentLength = 0;
  564. let itemsToInclude = [];
  565. let startIndex = lastIndex;
  566.  
  567. // Try to fit items starting from the last used index
  568. for (let i = 0; i < buyItems.length; i++) {
  569. const index = (startIndex + i) % buyItems.length;
  570. const item = buyItems[index];
  571. const newLength = currentLength + (currentLength > 0 ? 2 : 0) + item.length;
  572.  
  573. if (newLength <= maxLength) {
  574. itemsToInclude.push(item);
  575. currentLength = newLength;
  576. } else {
  577. break;
  578. }
  579. }
  580.  
  581. // Update the last used index for next time
  582. if (itemsToInclude.length > 0) {
  583. lastIndex = (startIndex + itemsToInclude.length) % buyItems.length;
  584. localStorage.setItem('lastBuyItemIndex', lastIndex.toString());
  585. }
  586.  
  587. return itemsToInclude.join(', ');
  588. }
  589.  
  590. function createUIPanel() {
  591. if (document.querySelector('.custom-ui-panel')) {
  592. return;
  593. }
  594.  
  595. const panel = document.createElement('div');
  596. panel.className = 'custom-ui-panel';
  597. panel.innerHTML = `
  598. <div class="tabs">
  599. <div class="tab active" data-tab="config-list-tab">Configured Buttons</div>
  600. <div class="tab" data-tab="config-edit-tab">Create/Edit Button</div>
  601. <div class="tab settings-tab" data-tab="config-settings-tab">⚙️</div>
  602. </div>
  603. <div id="config-list-tab" class="tab-content active">
  604. <div class="search-container">
  605. <input type="text" id="search-input" placeholder="Search...">
  606. <select id="search-select">
  607. <option value="buttonText">Text</option>
  608. <option value="condition">Condition</option>
  609. <option value="text">Message</option>
  610. </select>
  611. </div>
  612. <div id="button-configs"></div>
  613. </div>
  614. <div id="config-edit-tab" class="tab-content">
  615. <div>
  616. <label for="button-text">Button Text</label>
  617. <input type="text" id="button-text" placeholder="Button Text">
  618.  
  619. <label for="button-color">Background Color</label>
  620. <input type="color" id="button-color">
  621.  
  622. <label for="button-condition">Condition</label>
  623. <select id="button-condition">
  624. <option value="TradeChat">Trade Chat</option>
  625. <option value="HospitalChat">Hospital Chat</option>
  626. <option value="FactionChat">Faction Chat</option>
  627. <option value="CompanyChat">Company Chat</option>
  628. <option value="GlobalChat">Global Chat</option>
  629. <option value="UserChat">User Chat</option>
  630. </select>
  631.  
  632. <label for="button-text-content">Message</label>
  633. <textarea id="button-text-content" placeholder="Enter your message here. Use {name} for chatter's name, {company} for company info, {faction} for faction info, {bazaar} for bazaar info, {buy} for buy items."></textarea>
  634. <div class="char-counter" id="char-counter">0</div>
  635.  
  636. <button id="add-button">Add Button</button>
  637. <button id="edit-button" style="display: none;">Save Button</button>
  638. </div>
  639. </div>
  640. <div id="config-settings-tab" class="tab-content">
  641. <label for="api-key">API Key</label>
  642. <input type="text" id="api-key" placeholder="Enter your API key" value="${getAPIKey()}">
  643. <button id="save-api-key-button">Save API Key</button>
  644.  
  645. <label for="buy-items">Items to Buy (comma separated)</label>
  646. <input type="text" id="buy-items" placeholder="e.g., xanax, BCT, energy drinks" value="${getBuyItems()}">
  647. <button id="save-buy-items-button">Save Buy Items</button>
  648.  
  649. <button id="import-button">Import Config (File)</button>
  650. <button id="export-button">Export Config (File)</button>
  651. <button id="clear-cache-button">Clear API Cache</button>
  652. </div>
  653. <button id="close-ui">Close</button>
  654. `;
  655. document.body.appendChild(panel);
  656.  
  657. document.querySelectorAll('.tab').forEach(tab => {
  658. tab.addEventListener('click', () => {
  659. switchTab(tab.dataset.tab);
  660. });
  661. });
  662.  
  663. document.getElementById('add-button').addEventListener('click', addNewButtonConfig);
  664. document.getElementById('edit-button').addEventListener('click', editButtonConfig);
  665. document.getElementById('close-ui').addEventListener('click', closeUI);
  666. document.getElementById('import-button').addEventListener('click', importConfig);
  667. document.getElementById('export-button').addEventListener('click', exportConfig);
  668. document.getElementById('clear-cache-button').addEventListener('click', clearCache);
  669. document.getElementById('button-text-content').addEventListener('input', updateCharCounter);
  670. document.getElementById('search-input').addEventListener('input', filterButtonConfigs);
  671. document.getElementById('save-api-key-button').addEventListener('click', () => {
  672. const key = document.getElementById('api-key').value;
  673. saveAPIKey(key);
  674. });
  675. document.getElementById('save-buy-items-button').addEventListener('click', () => {
  676. const items = document.getElementById('buy-items').value;
  677. saveBuyItems(items);
  678. });
  679.  
  680. populateButtonConfigs();
  681. }
  682.  
  683. function switchTab(tabId) {
  684. document.querySelectorAll('.tab, .tab-content').forEach(el => {
  685. el.classList.remove('active');
  686. });
  687. document.querySelector(`[data-tab="${tabId}"]`).classList.add('active');
  688. document.getElementById(tabId).classList.add('active');
  689.  
  690. const panel = document.querySelector('.custom-ui-panel');
  691. if (tabId === 'config-list-tab') {
  692. panel.classList.add('config-list-tab-active');
  693. } else {
  694. panel.classList.remove('config-list-tab-active');
  695. }
  696. }
  697.  
  698. function populateButtonConfigs() {
  699. const configsContainer = document.getElementById('button-configs');
  700. configsContainer.innerHTML = '';
  701. const configs = getButtonConfigurations();
  702.  
  703. configs.buttons.forEach((buttonConfig, index) => {
  704. const configDiv = document.createElement('div');
  705. configDiv.className = 'button-config-card draggable';
  706. configDiv.dataset.index = index;
  707.  
  708. const textDiv = document.createElement('div');
  709. textDiv.innerHTML = `<strong>Text:</strong> ${buttonConfig.buttonText}`;
  710. configDiv.appendChild(textDiv);
  711.  
  712. const colorDiv = document.createElement('div');
  713. colorDiv.innerHTML = `<strong>Color:</strong> ${buttonConfig.backgroundColor}`;
  714. configDiv.appendChild(colorDiv);
  715.  
  716. const conditionDiv = document.createElement('div');
  717. conditionDiv.innerHTML = `<strong>Condition:</strong> ${buttonConfig.condition}`;
  718. configDiv.appendChild(conditionDiv);
  719.  
  720. const messageDiv = document.createElement('div');
  721. messageDiv.className = 'button-config-message';
  722. messageDiv.innerText = buttonConfig.text;
  723. configDiv.appendChild(messageDiv);
  724.  
  725. const editButton = document.createElement('button');
  726. editButton.textContent = 'Edit';
  727. editButton.addEventListener('click', () => {
  728. selectForEdit(index);
  729. switchTab('config-edit-tab');
  730. });
  731. configDiv.appendChild(editButton);
  732.  
  733. const deleteButton = document.createElement('button');
  734. deleteButton.textContent = 'Delete';
  735. deleteButton.addEventListener('click', () => deleteButtonConfig(index));
  736. configDiv.appendChild(deleteButton);
  737.  
  738. const moveUpButton = document.createElement('button');
  739. moveUpButton.textContent = 'Up';
  740. moveUpButton.addEventListener('click', () => moveButtonConfig(index, -1));
  741. configDiv.appendChild(moveUpButton);
  742.  
  743. const moveDownButton = document.createElement('button');
  744. moveDownButton.textContent = 'Down';
  745. moveDownButton.addEventListener('click', () => moveButtonConfig(index, 1));
  746. configDiv.appendChild(moveDownButton);
  747.  
  748. configsContainer.appendChild(configDiv);
  749. });
  750. }
  751.  
  752. function filterButtonConfigs() {
  753. const searchInput = document.getElementById('search-input').value.toLowerCase();
  754. const searchBy = document.getElementById('search-select').value;
  755. const configs = getButtonConfigurations();
  756.  
  757. const filteredConfigs = configs.buttons.filter(buttonConfig => {
  758. const fieldValue = buttonConfig[searchBy].toLowerCase();
  759. return fieldValue.includes(searchInput);
  760. });
  761.  
  762. const configsContainer = document.getElementById('button-configs');
  763. configsContainer.innerHTML = '';
  764.  
  765. filteredConfigs.forEach((buttonConfig, index) => {
  766. const configDiv = document.createElement('div');
  767. configDiv.className = 'button-config-card draggable';
  768. configDiv.dataset.index = index;
  769.  
  770. const textDiv = document.createElement('div');
  771. textDiv.innerHTML = `<strong>Text:</strong> ${buttonConfig.buttonText}`;
  772. configDiv.appendChild(textDiv);
  773.  
  774. const colorDiv = document.createElement('div');
  775. colorDiv.innerHTML = `<strong>Color:</strong> ${buttonConfig.backgroundColor}`;
  776. configDiv.appendChild(colorDiv);
  777.  
  778. const conditionDiv = document.createElement('div');
  779. conditionDiv.innerHTML = `<strong>Condition:</strong> ${buttonConfig.condition}`;
  780. configDiv.appendChild(conditionDiv);
  781.  
  782. const messageDiv = document.createElement('div');
  783. messageDiv.className = 'button-config-message';
  784. messageDiv.innerText = buttonConfig.text;
  785. configDiv.appendChild(messageDiv);
  786.  
  787. const editButton = document.createElement('button');
  788. editButton.textContent = 'Edit';
  789. editButton.addEventListener('click', () => {
  790. selectForEdit(index);
  791. switchTab('config-edit-tab');
  792. });
  793. configDiv.appendChild(editButton);
  794.  
  795. const deleteButton = document.createElement('button');
  796. deleteButton.textContent = 'Delete';
  797. deleteButton.addEventListener('click', () => deleteButtonConfig(index));
  798. configDiv.appendChild(deleteButton);
  799.  
  800. const moveUpButton = document.createElement('button');
  801. moveUpButton.textContent = 'Up';
  802. moveUpButton.addEventListener('click', () => moveButtonConfig(index, -1));
  803. configDiv.appendChild(moveUpButton);
  804.  
  805. const moveDownButton = document.createElement('button');
  806. moveDownButton.textContent = 'Down';
  807. moveDownButton.addEventListener('click', () => moveButtonConfig(index, 1));
  808. configDiv.appendChild(moveDownButton);
  809.  
  810. configsContainer.appendChild(configDiv);
  811. });
  812. }
  813.  
  814. function selectForEdit(index) {
  815. const config = getButtonConfigurations().buttons[index];
  816. document.getElementById('button-text').value = config.buttonText;
  817. document.getElementById('button-color').value = config.backgroundColor;
  818. document.getElementById('button-condition').value = config.condition;
  819. document.getElementById('button-text-content').value = config.text;
  820.  
  821. document.getElementById('add-button').style.display = 'block';
  822. document.getElementById('edit-button').style.display = 'block';
  823. document.getElementById('edit-button').setAttribute('data-edit-index', index);
  824. updateCharCounter();
  825. }
  826.  
  827. function deleteButtonConfig(index) {
  828. const config = getButtonConfigurations();
  829. config.buttons.splice(index, 1);
  830. saveButtonConfigurations(config);
  831. populateButtonConfigs();
  832. showNotification('Button deleted.');
  833. }
  834.  
  835. function moveButtonConfig(index, direction) {
  836. const config = getButtonConfigurations();
  837. const newIndex = index + direction;
  838.  
  839. if (newIndex >= 0 && newIndex < config.buttons.length) {
  840. const buttonConfig = config.buttons.splice(index, 1)[0];
  841. config.buttons.splice(newIndex, 0, buttonConfig);
  842. saveButtonConfigurations(config);
  843. populateButtonConfigs();
  844. showNotification('Button moved.');
  845. }
  846. }
  847.  
  848. function addNewButtonConfig() {
  849. const buttonText = document.getElementById('button-text').value;
  850. const backgroundColor = document.getElementById('button-color').value;
  851. const condition = document.getElementById('button-condition').value;
  852. const text = document.getElementById('button-text-content').value;
  853.  
  854. const config = getButtonConfigurations();
  855. config.buttons.push({ buttonText, backgroundColor, condition, text });
  856. saveButtonConfigurations(config);
  857. populateButtonConfigs();
  858. highlightButton(config.buttons.length - 1);
  859. switchTab('config-list-tab');
  860.  
  861. clearInputFields();
  862. showNotification('Button added.');
  863. }
  864.  
  865. function editButtonConfig() {
  866. const index = parseInt(document.getElementById('edit-button').getAttribute('data-edit-index'), 10);
  867. const buttonText = document.getElementById('button-text').value;
  868. const backgroundColor = document.getElementById('button-color').value;
  869. const condition = document.getElementById('button-condition').value;
  870. const text = document.getElementById('button-text-content').value;
  871.  
  872. const config = getButtonConfigurations();
  873. config.buttons[index] = { buttonText, backgroundColor, condition, text };
  874. saveButtonConfigurations(config);
  875. populateButtonConfigs();
  876. highlightButton(index);
  877. switchTab('config-list-tab');
  878.  
  879. document.getElementById('add-button').style.display = 'block';
  880. document.getElementById('edit-button').style.display = 'none';
  881.  
  882. clearInputFields();
  883. showNotification('Button edited.');
  884. }
  885.  
  886. function clearInputFields() {
  887. document.getElementById('button-text').value = '';
  888. document.getElementById('button-text-content').value = '';
  889. document.getElementById('button-color').value = '';
  890. updateCharCounter();
  891. }
  892.  
  893. function closeUI() {
  894. const panel = document.querySelector('.custom-ui-panel');
  895. if (panel) {
  896. panel.remove();
  897. }
  898. }
  899.  
  900. function createConfigButton() {
  901. const peopleButton = document.querySelector('#people_panel_button');
  902. if (!peopleButton || document.querySelector('#chat-config-button')) return;
  903.  
  904. const button = document.createElement('button');
  905. button.id = 'chat-config-button';
  906. button.type = 'button';
  907. button.title = 'Edit Chat Buttons';
  908. button.className = 'root___WHFbh root___J_YsG';
  909.  
  910. const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
  911. svg.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
  912. svg.setAttribute('viewBox', '0 0 512 512');
  913. svg.setAttribute('height', '24');
  914. svg.setAttribute('width', '24');
  915. svg.classList.add('root___DYylw', 'icon___M_Izz');
  916. svg.innerHTML = `
  917. <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)"/>
  918. <defs>
  919. <linearGradient id="config-default-blue" x1="0.5" x2="0.5" y2="1">
  920. <stop offset="0" stop-color="#8faeb4"/>
  921. <stop offset="1" stop-color="#638c94"/>
  922. </linearGradient>
  923. <linearGradient id="config-hover-blue" x1="0.5" x2="0.5" y2="1">
  924. <stop offset="0" stop-color="#eaf0f1"/>
  925. <stop offset="1" stop-color="#7b9fa6"/>
  926. </linearGradient>
  927. </defs>
  928. `;
  929.  
  930. button.appendChild(svg);
  931. button.addEventListener('click', createUIPanel);
  932.  
  933. const path = svg.querySelector('path');
  934. button.addEventListener('mouseenter', () => path.setAttribute('fill', 'url(#config-hover-blue)'));
  935. button.addEventListener('mouseleave', () => path.setAttribute('fill', 'url(#config-default-blue)'));
  936.  
  937. peopleButton.insertAdjacentElement('afterend', button);
  938. }
  939.  
  940. function applyButtonConfigurations() {
  941. const configs = getButtonConfigurations();
  942. // Query for both old and new chat window classes
  943. document.querySelectorAll('.root___FmdS_, .chat-box___mHm01').forEach(chatBox => {
  944. configs.buttons.forEach(buttonConfig => {
  945. const conditionFunc = conditions[buttonConfig.condition];
  946. if (conditionFunc && conditionFunc(chatBox) && !chatBox.querySelector(`[data-button-text="${buttonConfig.buttonText}"]`)) {
  947. const button = document.createElement('button');
  948. button.className = 'custom-chat-button';
  949. button.innerText = buttonConfig.buttonText;
  950. button.style.backgroundColor = buttonConfig.backgroundColor;
  951. button.setAttribute('data-button-text', buttonConfig.buttonText);
  952. button.addEventListener('click', (event) => addCustomText(chatBox, buttonConfig.text, event));
  953. button.addEventListener('mousedown', (event) => {
  954. if (event.button === 0) {
  955. let timer;
  956. const delay = 1000;
  957. timer = setTimeout(() => {
  958. button.classList.remove('recent');
  959. clearRecentButtonInfo();
  960. }, delay);
  961. button.addEventListener('mouseup', () => {
  962. clearTimeout(timer);
  963. }, { once: true });
  964. button.addEventListener('mouseleave', () => {
  965. clearTimeout(timer);
  966. }, { once: true });
  967. }
  968. });
  969.  
  970. // Try both new and old input container selectors
  971. const inputContainer = chatBox.querySelector('.root___WUd1h') || chatBox.querySelector('.chat-box-footer___YK914');
  972. if (inputContainer) {
  973. let buttonContainer = chatBox.querySelector('.button-container');
  974. if (!buttonContainer) {
  975. buttonContainer = document.createElement('div');
  976. buttonContainer.className = 'button-container';
  977. buttonContainer.style.display = 'flex';
  978. buttonContainer.style.flexWrap = 'wrap';
  979. inputContainer.insertAdjacentElement('beforebegin', buttonContainer);
  980. }
  981. buttonContainer.appendChild(button);
  982. }
  983. }
  984. });
  985. });
  986. }
  987.  
  988. async function addCustomText(chatBox, messageTemplate, event) {
  989. let name = 'User';
  990. // Try both new and old name selectors
  991. const titleElement = chatBox.querySelector('.title___Bmq5P') ||
  992. chatBox.querySelector('.typography___Dc5WV') ||
  993. chatBox.querySelector('.chat-box-header__name___jIjjM');
  994. if (titleElement) {
  995. name = titleElement.textContent.trim();
  996. }
  997. let message = messageTemplate.replace('{name}', name);
  998.  
  999. if (message.includes('{buy}')) {
  1000. const messageWithoutBuy = message.replace('{buy}', '');
  1001. const availableSpace = 125 - messageWithoutBuy.length;
  1002. const buyText = formatBuyItems(availableSpace);
  1003. message = message.replace('{buy}', buyText);
  1004. }
  1005.  
  1006. if (message.includes('{bazaar}')) {
  1007. const apiKey = getAPIKey();
  1008. if (!apiKey) {
  1009. alert('API key not set. Please set the API key in the settings tab.');
  1010. return;
  1011. }
  1012.  
  1013. let bazaarData = loadBazaarCache();
  1014. if (!bazaarData) {
  1015. const apiUrl = `https://api.torn.com/user/?key=${apiKey}&selections=bazaar`;
  1016. try {
  1017. const response = await fetch(apiUrl);
  1018. const data = await response.json();
  1019. if (!data.error) {
  1020. bazaarData = data;
  1021. saveBazaarData(bazaarData);
  1022. } else {
  1023. alert('Failed to retrieve bazaar information. Check your API key.');
  1024. return;
  1025. }
  1026. } catch (error) {
  1027. alert('Error fetching bazaar information:', error);
  1028. return;
  1029. }
  1030. }
  1031.  
  1032. // Calculate available space for bazaar list
  1033. const messageWithoutBazaar = message.replace('{bazaar}', '');
  1034. const availableSpace = 125 - messageWithoutBazaar.length;
  1035.  
  1036. const bazaarText = formatBazaarItems(bazaarData, availableSpace);
  1037. message = message.replace('{bazaar}', bazaarText);
  1038. }
  1039.  
  1040. if (message.includes('{company}')) {
  1041. const apiKey = getAPIKey();
  1042. if (!apiKey) {
  1043. alert('API key not set. Please set the API key in the settings tab.');
  1044. return;
  1045. }
  1046.  
  1047. let companyInfo = loadCache('companyCache');
  1048. if (!companyInfo) {
  1049. const apiUrl = `https://api.torn.com/company/?selections=profile&key=${apiKey}`;
  1050. try {
  1051. const response = await fetch(apiUrl);
  1052. const data = await response.json();
  1053. if (!data.error && data.company) {
  1054. companyInfo = data.company;
  1055. saveCache('companyCache', companyInfo);
  1056. } else {
  1057. alert('Failed to retrieve company information. Check your API key.');
  1058. return;
  1059. }
  1060. } catch (error) {
  1061. alert('Error fetching company information:', error);
  1062. return;
  1063. }
  1064. }
  1065.  
  1066. const companyType = companyTypes[companyInfo.company_type] || 'Unknown';
  1067. const companyDetails = `${companyInfo.rating}* ${companyType}`;
  1068. message = message.replace('{company}', companyDetails);
  1069. }
  1070.  
  1071. if (message.includes('{faction}')) {
  1072. const apiKey = getAPIKey();
  1073. if (!apiKey) {
  1074. alert('API key not set. Please set the API key in the settings tab.');
  1075. return;
  1076. }
  1077.  
  1078. let factionInfo = loadCache('factionCache');
  1079. if (!factionInfo) {
  1080. const apiUrl = `https://api.torn.com/faction/?selections=basic&key=${apiKey}`;
  1081. try {
  1082. const response = await fetch(apiUrl);
  1083. const data = await response.json();
  1084. if (!data.error && data.respect && data.name && data.rank) {
  1085. factionInfo = data;
  1086. saveCache('factionCache', factionInfo);
  1087. } else {
  1088. alert('Failed to retrieve faction information. Check your API key.');
  1089. return;
  1090. }
  1091. } catch (error) {
  1092. alert('Error fetching faction information:', error);
  1093. return;
  1094. }
  1095. }
  1096.  
  1097. const respectFormatted = factionInfo.respect >= 1000000 ? (factionInfo.respect / 1000000).toFixed(1) + 'm' : (factionInfo.respect / 1000).toFixed(1) + 'k';
  1098. const factionDetails = `${factionInfo.name}, ${factionInfo.rank.name} Ranked ${respectFormatted} Respect`;
  1099. message = message.replace('{faction}', factionDetails);
  1100. }
  1101.  
  1102. insertMessage(chatBox, message, event.target);
  1103. }
  1104.  
  1105. function insertMessage(chatBox, message, targetButton) {
  1106. navigator.clipboard.writeText(message).then(() => {
  1107. // Try both new and old textarea selectors
  1108. const textArea = chatBox.querySelector('.textarea___V8HsV') || chatBox.querySelector('textarea');
  1109. if (!textArea) return;
  1110. textArea.focus();
  1111. textArea.setRangeText(message, 0, textArea.value.length, 'end');
  1112. textArea.dispatchEvent(new Event('input', { bubbles: true }));
  1113. textArea.focus();
  1114. textArea.selectionStart = textArea.selectionEnd = message.length;
  1115.  
  1116. document.querySelectorAll('.custom-chat-button').forEach(btn => {
  1117. btn.classList.remove('recent');
  1118. });
  1119. targetButton.classList.add('recent');
  1120.  
  1121. // Try both new and old title selectors
  1122. const titleElement = chatBox.querySelector('.title___Bmq5P') ||
  1123. chatBox.querySelector('.chat-box-header__name___jIjjM');
  1124. const chatBoxName = titleElement ? titleElement.textContent.trim() : '';
  1125. saveRecentButtonInfo(targetButton.getAttribute('data-button-text'), chatBoxName);
  1126. });
  1127. }
  1128.  
  1129. function applyRecentButtonClass() {
  1130. const recentButtonInfo = JSON.parse(localStorage.getItem('recentButtonInfo'));
  1131. if (recentButtonInfo) {
  1132. document.querySelectorAll('.custom-chat-button').forEach(btn => {
  1133. btn.classList.remove('recent');
  1134. });
  1135.  
  1136. // Query for both old and new chat window classes
  1137. document.querySelectorAll('.root___FmdS_, .chat-box___mHm01').forEach(chatBox => {
  1138. // Try both new and old title selectors
  1139. const titleElement = chatBox.querySelector('.title___Bmq5P') ||
  1140. chatBox.querySelector('.chat-box-header__name___jIjjM');
  1141. const chatBoxName = titleElement ? titleElement.textContent.trim() : '';
  1142. if (chatBoxName === recentButtonInfo.chatBoxName) {
  1143. const button = chatBox.querySelector(`[data-button-text="${recentButtonInfo.buttonText}"]`);
  1144. if (button) {
  1145. button.classList.add('recent');
  1146. }
  1147. }
  1148. });
  1149. }
  1150. }
  1151.  
  1152. function exportConfig() {
  1153. const config = {
  1154. ...getButtonConfigurations(),
  1155. buyItems: getBuyItems()
  1156. };
  1157. const blob = new Blob([JSON.stringify(config, null, 2)], { type: 'application/json' });
  1158. const url = URL.createObjectURL(blob);
  1159. const a = document.createElement('a');
  1160. a.href = url;
  1161. a.download = 'chatButtonConfig.json';
  1162. document.body.appendChild(a);
  1163. a.click();
  1164. document.body.removeChild(a);
  1165. URL.revokeObjectURL(url);
  1166. showNotification('Configuration exported to file.');
  1167. }
  1168.  
  1169. function importConfig() {
  1170. const input = document.createElement('input');
  1171. input.type = 'file';
  1172. input.accept = '.json';
  1173. input.onchange = async (event) => {
  1174. const file = event.target.files[0];
  1175. if (!file) {
  1176. showNotification('No file selected.');
  1177. return;
  1178. }
  1179. const reader = new FileReader();
  1180. reader.onload = (e) => {
  1181. try {
  1182. const config = JSON.parse(e.target.result);
  1183. if (config && config.buttons) {
  1184. saveButtonConfigurations(config);
  1185. if (config.buyItems) {
  1186. saveBuyItems(config.buyItems);
  1187. document.getElementById('buy-items').value = config.buyItems;
  1188. }
  1189. populateButtonConfigs();
  1190. applyButtonConfigurations();
  1191. showNotification('Configuration imported from file.');
  1192. } else {
  1193. showNotification('Invalid configuration file.');
  1194. }
  1195. } catch (err) {
  1196. showNotification('Error: Invalid JSON.');
  1197. }
  1198. };
  1199. reader.readAsText(file);
  1200. };
  1201. input.click();
  1202. }
  1203.  
  1204. function updateCharCounter() {
  1205. const textArea = document.getElementById('button-text-content');
  1206. if (!textArea) return;
  1207. const counter = document.getElementById('char-counter');
  1208. if (!counter) return;
  1209. counter.textContent = textArea.value.length;
  1210. }
  1211.  
  1212. function highlightButton(index) {
  1213. const configsContainer = document.getElementById('button-configs');
  1214. const buttonDiv = configsContainer.querySelector(`.draggable[data-index="${index}"]`);
  1215. if (buttonDiv) {
  1216. buttonDiv.classList.add('highlight');
  1217. buttonDiv.scrollIntoView({ behavior: 'smooth', block: 'center' });
  1218. setTimeout(() => {
  1219. buttonDiv.classList.remove('highlight');
  1220. }, 2000);
  1221. }
  1222. }
  1223.  
  1224. addCSS(buttonCSS);
  1225.  
  1226. const chatContainerObserver = new MutationObserver(function() {
  1227. createConfigButton();
  1228. applyButtonConfigurations();
  1229. applyRecentButtonClass();
  1230. });
  1231.  
  1232. const chatContainer = document.querySelector('#chatRoot');
  1233. if (chatContainer) {
  1234. chatContainerObserver.observe(chatContainer, { childList: true, subtree: true });
  1235. }
  1236.  
  1237. applyButtonConfigurations();
  1238. applyRecentButtonClass();
  1239. })();