您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
GUI to configure Torn racing parameters and create races with presets and quick launch buttons. Includes race list filtering.
// ==UserScript== // @name Torn Race Manager // @version 3.8.1 // @description GUI to configure Torn racing parameters and create races with presets and quick launch buttons. Includes race list filtering. // @author GNSC4 [268863] // @match https://www.torn.com/loader.php?sid=racing* // @match https://www.torn.com/* // @match https://api.torn.com/* // @exclude https://www.torn.com/loader.php?sid=crimes* // @grant GM.xmlHttpRequest // @grant GM_xmlhttpRequest // @grant GM.setValue // @grant GM.getValue // @grant GM_setValue // @grant GM_getValue // @run-at document-end // @require https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js // @icon https://www.google.com/s2/favicons?sz=64&domain=torn.com // @license MIT // @namespace torn.raceconfigguipda // ==/UserScript== (function() { 'use strict'; const DEBUG = true; // Check if we're on an attack page and exit early if true if (window.location.href.includes('sid=getInAttack') || window.location.href.includes('sid=attack') || window.location.href.includes('loader2.php') || window.location.href.includes('sid=travel') || window.location.pathname.includes('loader2.php')) { if (DEBUG) console.log('Race Manager: Not initializing on this page type.'); return; } if (typeof document === 'undefined') { if (DEBUG) console.error('Document object not available yet. Script may not run correctly.'); return; } const trackNames = { '6': 'Uptown', '7': 'Withdrawal', '8': 'Underdog', '9': 'Parkland', '10': 'Docks', '11': 'Commerce', '12': 'Two Islands', '15': 'Industrial', '16': 'Vector', '17': 'Mudpit', '18': 'Hammerhead', '19': 'Sewage', '20': 'Meltdown', '21': 'Speedway', '23': 'Stone Park', '24': 'Convict' }; let guiInitialized = false; let domCheckAttempts = 0; const MAX_DOM_CHECK_ATTEMPTS = 100; const STORAGE_API_KEY = 'raceConfigAPIKey_release_NoGMf'; const style = document.createElement('style'); style.textContent = ` #raceConfigGUI { position: fixed; top: 85px; left: 20px; background-color: #222; color: #ddd; border: 1px solid #555; padding: 25px; z-index: 999999 !important; font-family: Arial, sans-serif; border-radius: 10px; max-width: 500px; max-height: 90vh; overflow-y: auto; display: none; user-select: none; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.6); scrollbar-width: thin; scrollbar-color: #444 #222; } /* Webkit Scrollbar Styling */ #raceConfigGUI::-webkit-scrollbar { width: 8px; height: 8px; } #raceConfigGUI::-webkit-scrollbar-track { background: #222; border-radius: 4px; } #raceConfigGUI::-webkit-scrollbar-thumb { background: #444; border-radius: 4px; border: 2px solid #222; } #raceConfigGUI::-webkit-scrollbar-thumb:hover { background: #555; } #raceConfigGUI::-webkit-scrollbar-corner { background: #222; } #raceConfigGUI .api-key-section, #raceConfigGUI .config-section, #raceConfigGUI .car-select-section, #raceConfigGUI .presets-section { margin-bottom: 25px; padding: 15px; background-color: #2a2a2a; border-radius: 8px; border: 1px solid #444; position: relative; z-index: 999999 !important; } #raceConfigGUI h2, #raceConfigGUI h3, #raceConfigGUI h4 { color: #fff; font-size: 1.2em; margin: 0 0 15px 0; padding-bottom: 10px; border-bottom: 1px solid #444; text-align: center; } #raceConfigGUI input[type="text"], #raceConfigGUI input[type="number"], #raceConfigGUI input[type="password"], #raceConfigGUI input[type="date"], #raceConfigGUI input[type="time"], #raceConfigGUI select { padding: 8px 12px; margin: 5px 0; border: 1px solid #555; background-color: #333 !important; color: #eee !important; border-radius: 5px; width: calc(100% - 26px); font-size: 14px; -webkit-text-fill-color: #eee !important; transition: background-color 0.3s ease, border-color 0.3s ease; box-shadow: 0 0 0 1000px #333 inset !important; } #raceConfigGUI input:-webkit-autofill, #raceConfigGUI input:-webkit-autofill:hover, #raceConfigGUI input:-webkit-autofill:focus, #raceConfigGUI input:-webkit-autofill:active { -webkit-box-shadow: 0 0 0 1000px #333 inset !important; -webkit-text-fill-color: #eee !important; transition: background-color 0s 50000s; caret-color: #eee !important; } #raceConfigGUI input:focus, #raceConfigGUI select:focus { border-color: #666; outline: none; box-shadow: 0 0 5px rgba(85, 85, 85, 0.5); } #raceConfigGUI label { display: block; margin-bottom: 5px; color: #ccc; font-size: 14px; } .gui-button, .preset-button, #toggleRaceGUIButton, #createRaceButton, #closeGUIButton, #setNowButton { color: #ddd; background-color: #555; border: 1px solid #777; border-radius: 3px; padding: 8px 15px; cursor: pointer; margin: 5px; transition: background-color 0.3s ease; font-size: 0.9em; display: inline-block; text-decoration: none; } .gui-button:hover, .preset-button:hover, .remove-preset:hover, #toggleRaceGUIButton:hover, #createRaceButton:hover, #closeGUIButton:hover, #setNowButton:hover { background-color: #777; } #createRaceButton { background-color: #2d5a3f !important; border-color: #3d7a5f !important; font-size: 14px !important; padding: 12px 24px !important; margin: 15px auto !important; display: block !important; width: 80% !important; } #createRaceButton:hover { background-color: #3d7a5f !important; } .preset-buttons-container { display: grid; grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); gap: 8px; margin-bottom: 15px; padding: 5px; width: 100%; box-sizing: border-box; } .preset-button-container { position: relative; display: inline-flex; flex-direction: column; align-items: stretch; margin-bottom: 10px; text-align: center; } .remove-preset { background-color: #955; color: #eee; padding: 0; border-radius: 50%; font-size: 14px; position: absolute; top: -8px; right: -8px; width: 20px; height: 20px; display: flex; align-items: center; justify-content: center; cursor: pointer; border: none; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3); text-decoration: none; } .remove-preset:hover { background-color: #c77; transform: scale(1.1); transition: all 0.2s ease; text-decoration: none; } #statusMessageBox { margin-top: 15px; padding: 12px; border-radius: 5px; font-size: 14px; text-align: center; } #statusMessageBox.success { background-color: #1a472a; border: 1px solid #2d5a3f; } #statusMessageBox.error { background-color: #5c1e1e; border: 1px solid #8b2e2e; } .driver-inputs-container { display: flex; gap: 10px; margin-bottom: 10px; } .driver-input-wrapper { flex: 1; min-width: 0; margin-right: 5px; } .driver-input-wrapper:last-child { margin-right: 0; } .preset-actions { display: flex; justify-content: center; gap: 10px; margin-top: 15px; } .api-key-wrapper { display: flex; justify-content: flex-start; align-items: center; gap: 10px; margin: 0 auto; max-width: 400px; position: relative; } .api-key-wrapper label { display: inline; margin-bottom: 0; white-space: nowrap; min-width: 65px; } #closeGUIButton { position: absolute; top: -15px; right: -15px; width: 30px; height: 30px; border-radius: 50%; background-color: #555; color: #ddd; border: 1px solid #777; display: flex; align-items: center; justify-content: center; cursor: pointer; font-size: 18px; padding: 0; z-index: 1000000; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3); } #closeGUIButton:hover { background-color: #777; transform: scale(1.1); transition: all 0.2s ease; } .banner-container { position: relative; margin-bottom: 25px; padding-top: 5px; } #raceBanner { width: 100%; height: auto; border-radius: 5px; display: block; margin-bottom: 15px; } #raceConfigGUI h2 { margin-top: 10px; } .show-password-btn { background: none; border: none; color: #777; cursor: pointer; padding: 5px; font-size: 14px; position: absolute; right: 80px; top: 50%; transform: translateY(-50%); transition: color 0.3s ease; } .show-password-btn:hover { color: #999; } `; style.textContent += ` .quick-launch-container { position: relative !important; display: flex !important; flex-direction: column !important; width: 100% !important; max-width: 800px !important; gap: 5px !important; margin-top: 5px !important; margin-bottom: 10px !important; background-color: #2a2a2a !important; padding: 10px !important; border-radius: 5px !important; border: 1px solid #444 !important; z-index: 1 !important; } .quick-launch-container:not(:empty) { justify-content: flex-start !important; } .quick-launch-container .button-container { display: flex !important; flex-wrap: wrap !important; gap: 5px !important; width: 100% !important; } .quick-launch-button { color: #fff !important; background-color: #555 !important; border: 1px solid #777 !important; border-radius: 3px !important; padding: 5px 10px !important; cursor: pointer !important; font-size: 0.9em !important; white-space: nowrap !important; width: auto !important; display: inline-block !important; transition: all 0.2s ease !important; margin: 2px !important; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2) !important; flex-shrink: 0 !important; } .quick-launch-button:hover { background-color: #3d7a5f !important; border-color: #777 !important; transform: translateY(-1px) !important; box-shadow: 0 2px 5px rgba(0, 0, 0, 0.3) !important; } .quick-launch-status { position: relative !important; margin-top: 5px !important; padding: 10px 15px !important; border-radius: 5px !important; color: #fff !important; font-size: 14px !important; opacity: 0 !important; transition: opacity 0.3s ease !important; text-align: center !important; width: calc(100% - 30px) !important; background-color: transparent !important; z-index: 999999 !important; margin-left: auto !important; margin-right: auto !important; display: block !important; min-height: 20px !important; } .quick-launch-status.success { background-color: #1a472a !important; border: 1px solid #2d5a3f !important; opacity: 1 !important; } .quick-launch-status.error { background-color: #5c1e1e !important; border: 1px solid #8b2e2e !important; opacity: 1 !important; } .quick-launch-status.info { background-color: #2a2a2a !important; border: 1px solid #444 !important; opacity: 1 !important; } .quick-launch-status.show { opacity: 1 !important; } `; style.textContent += ` #raceToggleRow { display: flex !important; align-items: center !important; gap: 10px !important; width: 100% !important; flex-direction: row !important; position: relative !important; z-index: 100 !important; margin-bottom: 5px !important; } .button-container-wrapper { display: inline-flex !important; align-items: center !important; gap: 10px !important; margin-right: auto !important; } `; style.textContent += ` .time-config { display: flex; flex-direction: column; gap: 10px; } .time-selector { display: flex; align-items: center; gap: 5px; } .time-save-option { display: flex; align-items: center; gap: 5px; margin-top: 5px; padding: 5px; background: #333; border-radius: 4px; } .time-save-option input[type="checkbox"] { margin: 0; } .car-select-section .car-input-container { display: flex; gap: 10px; align-items: flex-start; } .car-select-section .car-id-wrapper { flex: 0 0 30%; } .car-select-section .car-dropdown-wrapper { flex: 0 0 70%; } .car-select-section input, .car-select-section select { width: 100% !important; box-sizing: border-box; } `; style.textContent += ` .filter-options { margin-bottom: 10px; } .filter-row { display: flex !important; align-items: center !important; gap: 10px !important; margin-bottom: 10px !important; padding: 10px; background: #2a2a2a; border-radius: 5px; } .filter-buttons { margin-left: auto; display: flex; gap: 10px; align-items: center; } .filter-row select { min-width: 150px; } .filter-row .gui-button { padding: 5px 10px; height: 30px; margin-left: auto; } .gui-button.active { background-color: #2d5a3f !important; border-color: #3d7a5f !important; } .races-list { max-height: 300px; overflow-y: auto; } .race-item { display: flex; justify-content: space-between; align-items: center; padding: 8px; border-bottom: 1px solid #444; } .race-info { display: flex; gap: 15px; } .join-race-btn { padding: 4px 8px; background: #2d5a3f; border: 1px solid #3d7a5f; color: white; cursor: pointer; border-radius: 3px; } .join-race-btn:hover { background: #3d7a5f; } `; style.textContent += ` .race-filter-controls { background-color: #2a2a2a !important; border: 1px solid #444 !important; border-radius: 8px !important; padding: 10px !important; margin-bottom: 15px !important; } .race-filter-controls .filter-row { background-color: transparent !important; padding: 0 !important; gap: 10px !important; flex-wrap: wrap !important; margin-bottom: 5px !important; } .race-filter-controls .filter-group { flex: 1 !important; min-width: 200px !important; margin-bottom: 5px !important; } .race-filter-controls .filter-group.laps-filter { min-width: 140px !important; display: flex !important; gap: 5px !important; align-items: center !important; } .race-filter-controls .filter-group.laps-filter input[type="number"] { width: 50px !important; padding: 8px 5px !important; text-align: center !important; } .race-filter-controls .filter-buttons { flex: 0 0 100% !important; display: flex !important; justify-content: flex-end !important; gap: 5px !important; margin-top: 5px !important; } .race-filter-controls .checkboxes { display: flex !important; flex-direction: row !important; gap: 15px !important; margin-bottom: 0 !important; } .race-filter-controls select, .race-filter-controls input[type="number"] { background-color: #333 !important; color: #eee !important; border: 1px solid #555 !important; border-radius: 4px !important; padding: 5px !important; } .race-filter-controls select:focus, .race-filter-controls input[type="number"]:focus { border-color: #666 !important; outline: none !important; box-shadow: 0 0 5px rgba(85, 85, 85, 0.5) !important; } .race-filter-controls label { color: #ccc !important; font-size: 0.9em !important; } .race-filter-controls .checkbox-option { color: #ccc !important; } .race-filter-controls .gui-button { background-color: #444 !important; color: #eee !important; border: 1px solid #555 !important; } .race-filter-controls .gui-button:hover { background-color: #555 !important; } .race-filter-controls .gui-button.active { background-color: #2d5a3f !important; border-color: #3d7a5f !important; } `; style.textContent += ` .preset-section-header { color: #fff !important; font-size: 14px !important; margin: 15px 0 5px 0 !important; padding: 5px 10px !important; background-color: #2a2a2a !important; border-radius: 3px !important; border: 1px solid #444 !important; } `; style.textContent += ` #minimizeQuickLaunchButton { position: absolute !important; top: 2px !important; right: 2px !important; width: 30px !important; height: 30px !important; background-color: #444 !important; color: white !important; border: 1px solid #666 !important; border-radius: 4px !important; font-size: 16px !important; text-align: center !important; line-height: 30px !important; cursor: pointer !important; z-index: 1000000 !important; display: flex !important; align-items: center !important; justify-content: center !important; transition: all 0.2s ease !important; box-shadow: 0 1px 3px rgba(0,0,0,0.3) !important; user-select: none !important; pointer-events: auto !important; } #minimizeQuickLaunchButton:hover { background-color: #555 !important; border-color: #888 !important; transform: translateY(-1px) !important; box-shadow: 0 2px 5px rgba(0,0,0,0.4) !important; } #minimizeQuickLaunchButton:active { transform: translateY(0px) !important; background-color: #333 !important; box-shadow: 0 1px 2px rgba(0,0,0,0.4) !important; } #minimizeQuickLaunchButtonContent { pointer-events: none !important; width: 100% !important; height: 100% !important; display: flex !important; align-items: center !important; justify-content: center !important; } .quick-launch-container.minimized { padding: 5px !important; max-height: 35px !important; overflow: hidden !important; position: relative !important; } .quick-launch-container.minimized .button-container, .quick-launch-container.minimized .preset-section-header:not(:first-child) { display: none !important; visibility: hidden !important; height: 0 !important; opacity: 0 !important; overflow: hidden !important; } .quick-launch-container.minimized .preset-section-header:first-child { display: block !important; visibility: visible !important; opacity: 1 !important; margin: 0 !important; } .quick-launch-container .preset-section-header:first-child { margin-top: 0 !important; margin-bottom: 0 !important; padding-right: 40px !important; z-index: 1 !important; pointer-events: none !important; } .quick-launch-container .preset-section-header:first-child > span { pointer-events: auto !important; display: inline-block !important; } `; document.head.appendChild(style); function init() { const isRacingPage = window.location.href.includes('sid=racing'); if (isRacingPage) { initializeRacingFeatures(); initializeRaceFiltering(); } } function initializeRacingFeatures() { const pollForElements = () => { const titleElement = document.querySelector('div.content-title > h4'); if (titleElement) { createToggleButton(); loadApiKey(); loadPresets(); setTimeout(() => { updateCarList().then(() => { updateQuickLaunchButtons(); if (DEBUG) console.log('Race Config GUI car list updated'); }).catch(err => { if (DEBUG) console.warn('Failed to update car list, but continuing:', err); }); }, 1500); if (DEBUG) console.log('Race Config GUI initialized'); } else if (domCheckAttempts < MAX_DOM_CHECK_ATTEMPTS) { domCheckAttempts++; setTimeout(pollForElements, 100); } }; if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', pollForElements); } else { pollForElements(); } } function initializeScript() { if (window.guiInitialized) { if (DEBUG) console.warn('GUI already initialized'); return; } const raceConfigGUI = createRaceConfigGUI(); document.body.appendChild(raceConfigGUI); initializeGUI(raceConfigGUI); createToggleButton(); window.guiInitialized = true; if (DEBUG) console.log('Race Config GUI initialized successfully'); } function createRaceConfigGUI() { let gui = document.createElement('div'); gui.id = 'raceConfigGUI'; gui.innerHTML = ` <div class="banner-container"> <button type="button" id="closeGUIButton" class="close-button" title="Close GUI">×</button> <img id="raceBanner" src="https://www.torn.com/images/v2/racing/header/banners/976_classA.png" alt="Racing Banner"> <h2>Race Configuration</h2> </div> <div class="api-key-section"> <h4>Settings & API Key</h4> <div class="api-key-wrapper"> <label for="apiKeyInput">API Key:</label> <input type="password" id="apiKeyInput" placeholder="Enter your API Key" autocomplete="new-password" autocapitalize="off" autocorrect="off" spellcheck="false" style="flex: 1;"> <button type="button" class="show-password-btn" id="showApiKey" title="Show/Hide API Key">👁️</button> <button id="saveApiKeyButton" class="gui-button">Save</button> </div> </div> <div class="config-section"> <h4>Race Settings</h4> <div style="display: flex; gap: 10px; margin-bottom: 10px;"> <div style="flex: 2;"> <label for="trackSelect">Track:</label> <select id="trackSelect"> <option value="6">6 - Uptown</option> <option value="7">7 - Withdrawal</option> <option value="8">8 - Underdog</option> <option value="9">9 - Parkland</option> <option value="10">10 - Docks</option> <option value="11">11 - Commerce</option> <option value="12">12 - Two Islands</option> <option value="15">15 - Industrial</option> <option value="16">16 - Vector</option> <option value="17">17 - Mudpit</option> <option value="18">18 - Hammerhead</option> <option value="19">19 - Sewage</option> <option value="20">20 - Meltdown</option> <option value="21">21 - Speedway</option> <option value="23">23 - Stone Park</option> <option value="24">24 - Convict</option> </select> </div> <div style="flex: 1;"> <label for="lapsInput">Laps:</label> <input type="number" id="lapsInput" value="100" min="1" max="100"> </div> </div> <div class="config-params-section"> <div class="driver-inputs-container"> <div class="driver-input-wrapper"> <label for="minDriversInput">Min Drivers:</label> <input type="number" id="minDriversInput" value="2" min="2" max="10"> </div> <div class="driver-input-wrapper"> <label for="maxDriversInput">Max Drivers:</label> <input type="number" id="maxDriversInput" value="2" min="2" max="10"> </div> <div class="driver-input-wrapper"> <label for="betAmountInput">Bet: <span style="font-size: 0.8em; color: #ccc;">(Max 10M)</span></label> <input type="number" id="betAmountInput" value="0" min="0" max="10000000"> </div> </div> </div> <div><label for="raceNameInput">Race Name: <span style="font-size: 0.8em; color: #ccc;">(Required)</span></label> <input type="text" id="raceNameInput" placeholder="Enter Race Name" pattern="[A-Za-z0-9 ]+" title="Only letters, numbers and spaces allowed" autocomplete="off" oninput="this.value = this.value.replace(/[^A-Za-z0-9 ]/g, '')"></div> <div><label for="passwordInput">Password: <span style="font-size: 0.8em; color: #ccc;">(Optional)</span></label> <input type="text" id="passwordInput" placeholder="Race Password Optional" autocomplete="off" autocapitalize="off" autocorrect="off" spellcheck="false"></div> <div class="time-config"> <label>Race Start Time (TCT 24hr):</label> <div class="time-selector"> <select id="hourSelect" style="width: auto; display: inline-block;"></select> <span style="margin: 0 5px;">:</span> <select id="minuteSelect" style="width: auto; display: inline-block;"></select> <button id="setNowButton" class="gui-button" style="padding: 5px 10px; font-size: 0.8em; margin-left: 5px; vertical-align: baseline;">NOW</button> </div> <div class="time-save-option"> <input type="checkbox" id="saveTimeToPreset"> <label for="saveTimeToPreset">Save time to preset</label> </div> </div> </div> <div class="car-select-section config-section"> <h4>Car Selection</h4> <div class="car-input-container"> <div class="car-id-wrapper"> <label for="carIdInput">Car ID:</label> <input type="text" id="carIdInput" placeholder="Enter Car ID" style="margin-right: 5px;"> </div> <div class="car-dropdown-wrapper"> <label for="carDropdown">Car:</label> <select id="carDropdown"> <option value="">Select a car...</option> </select> </div> </div> <div style="text-align: center; margin-top: 10px;"> <button id="updateCarsButton" class="gui-button" style="width: 80%; max-width: 200px; display: block; margin: 0 auto;">Update Cars</button> <div id="carStatusMessage" style="font-size: 0.8em; color: #aaa; margin-top: 5px;"></div> </div> </div> <div class="presets-section config-section"> <h4>Presets</h4> <div id="presetButtonsContainer" class="preset-buttons-container"> </div> <div class="preset-actions"> <button id="savePresetButton" class="gui-button">Save Preset</button> <button id="clearPresetsButton" class="gui-button">Clear Presets</button> </div> <div id="statusMessageBox" style="display:none;">Status Message</div> </div> <div class="action-buttons" style="text-align: center; margin-top: 15px;"> <button id="createRaceButton" class="gui-button">Create Race</button> </div> <div style="text-align: center; margin-top: 20px; color: #888; font-size: 1.2em;"> Script created by <a href="https://www.torn.com/profiles.php?XID=268863" target="_blank" style="color: #888; text-decoration: none;">GNSC4 [268863]</a><br> <a href="https://www.torn.com/forums.php#/p=threads&f=67&t=16454445&b=0&a=0" target="_blank" style="color: #888; text-decoration: none;">v3.8.0 Official Forum Link</a> </div> `; gui.addEventListener('touchstart', function(e) { if (e.target.closest('.drag-handle') || e.target.closest('button')) { e.stopPropagation(); } }, { passive: true }); gui.addEventListener('touchmove', function(e) { }, { passive: true }); const isMinimized = GM_getValue('raceConfigGUIMinimized', false); if (isMinimized) { gui.classList.add('minimized'); } return gui; } function initializeGUI(gui) { loadApiKey(); populateTimeDropdowns(); updateCarDropdown(); loadPresets(); const apiKeyInput = document.getElementById('apiKeyInput'); const saveApiKeyButton = document.getElementById('saveApiKeyButton'); const trackSelect = document.getElementById('trackSelect'); const lapsInput = document.getElementById('lapsInput'); const minDriversInput = document.getElementById('minDriversInput'); const maxDriversInput = document.getElementById('maxDriversInput'); const raceNameInput = document.getElementById('raceNameInput'); const passwordInput = document.getElementById('passwordInput'); const betAmountInput = document.getElementById('betAmountInput'); const hourSelect = document.getElementById('hourSelect'); const minuteSelect = document.getElementById('minuteSelect'); const setNowButton = document.getElementById('setNowButton'); const carIdInput = document.getElementById('carIdInput'); const carDropdown = document.getElementById('carDropdown'); const updateCarsButton = document.getElementById('updateCarsButton'); const carStatusMessage = document.getElementById('carStatusMessage'); const savePresetButton = document.getElementById('savePresetButton'); const clearPresetsButton = document.getElementById('clearPresetsButton'); const presetButtonsContainer = document.getElementById('presetButtonsContainer'); const statusMessageBox = document.getElementById('statusMessageBox'); const createRaceButton = document.getElementById('createRaceButton'); const closeGUIButton = document.getElementById('closeGUIButton'); if (saveApiKeyButton) { saveApiKeyButton.addEventListener('click', () => { saveApiKey(); }); } else { if (DEBUG) console.error("Error: saveApiKeyButton element not found in initializeGUI"); } if (setNowButton) { setNowButton.addEventListener('click', () => { setTimeToNow(); }); } else { if (DEBUG) console.error("Error: setNowButton element not found in initializeGUI"); } if (updateCarsButton) { updateCarsButton.addEventListener('click', () => { updateCarList(); }); } else { if (DEBUG) console.error("Error: updateCarsButton element not found in initializeGUI"); } if (carDropdown) { carDropdown.addEventListener('change', () => { carIdInput.value = carDropdown.value; }); } else { if (DEBUG) console.error("Error: carDropdown element not found in initializeGUI"); } if (savePresetButton) { savePresetButton.addEventListener('click', () => { savePreset(); }); } else { if (DEBUG) console.error("Error: savePresetButton element not found in initializeGUI"); } if (clearPresetsButton) { clearPresetsButton.addEventListener('click', () => { clearPresets(); }); } else { if (DEBUG) console.error("Error: clearPresetsButton element not found in initializeGUI"); } if (createRaceButton) { createRaceButton.addEventListener('click', () => { createRace(); }); } else { if (DEBUG) console.error("Error: createRaceButton element not found in initializeGUI"); } if (closeGUIButton) { closeGUIButton.addEventListener('click', () => { toggleRaceGUI(); }); } else { if (DEBUG) console.error("Error: closeGUIButton element not found in initializeGUI"); } if (carDropdown && carIdInput) { carDropdown.addEventListener('change', () => { const value = carDropdown.value.trim(); if (value && carDropdown.querySelector(`option[value="${value}"]`)) { carDropdown.value = value; } else { carDropdown.value = ''; } }); carIdInput.addEventListener('input', () => { const value = carIdInput.value.trim(); if (value && carDropdown.querySelector(`option[value="${value}"]`)) { carDropdown.value = value; } else { carDropdown.value = ''; } }); } if (document.getElementById('showApiKey')) { document.getElementById('showApiKey').addEventListener('click', function() { const apiKeyInput = document.getElementById('apiKeyInput'); const type = apiKeyInput.getAttribute('type') === 'password' ? 'text' : 'password'; apiKeyInput.setAttribute('type', type); this.textContent = type === 'password' ? '�️' : '👁️🗨️'; }); } dragElement(gui); displayPresets(); updateQuickPresetsDisplay(); updateQuickLaunchButtons(); displayStatusMessage('GUI Loaded', 'success'); setTimeout(() => displayStatusMessage('', ''), 3000); } function createToggleButton() { const existingButton = document.getElementById('toggleRaceGUIButton'); if (existingButton) { if (DEBUG) console.log('Toggle button already exists'); return existingButton; } const titleElement = document.querySelector('div.content-title > h4'); if (!titleElement) { if (DEBUG) console.error('Title element not found'); return null; } const wrapper = document.createElement('div'); wrapper.style.cssText = ` display: flex !important; flex-direction: column !important; width: 100% !important; margin-bottom: 10px !important; `; const topRow = document.createElement('div'); topRow.id = 'raceToggleRow'; const buttonContainer = document.createElement('div'); buttonContainer.className = 'button-container-wrapper'; const button = document.createElement('button'); button.id = 'toggleRaceGUIButton'; button.className = 'gui-button'; button.textContent = 'Race Config'; const quickLaunchContainer = document.createElement('div'); quickLaunchContainer.id = 'quickLaunchContainer'; quickLaunchContainer.className = 'quick-launch-container'; buttonContainer.appendChild(button); topRow.appendChild(buttonContainer); wrapper.appendChild(topRow); wrapper.appendChild(quickLaunchContainer); titleElement.parentNode.insertBefore(wrapper, titleElement.nextSibling); button.addEventListener('click', () => { if (DEBUG) console.log('Toggle button clicked'); toggleRaceGUI(); }); updateQuickLaunchButtons(); return button; } function setBodyScroll(disable) { document.body.style.overflow = disable ? 'hidden' : ''; document.body.style.position = disable ? 'fixed' : ''; document.body.style.width = disable ? '100%' : ''; } function toggleRaceGUI() { let gui = document.getElementById('raceConfigGUI'); if (gui) { const isVisible = gui.style.display === 'none'; gui.style.display = isVisible ? 'block' : 'none'; setBodyScroll(isVisible); if (DEBUG) console.log('Toggling existing GUI:', gui.style.display); } else { if (DEBUG) console.log('Creating new GUI'); gui = createRaceConfigGUI(); document.body.appendChild(gui); initializeGUI(gui); gui.style.display = 'block'; setBodyScroll(true); } } function dragElement(elmnt) { const dragHandle = document.createElement('div'); dragHandle.className = 'drag-handle'; dragHandle.style.cssText = ` position: absolute; top: 0; left: 0; right: 40px; height: 40px; cursor: move; background: transparent; pointer-events: all; z-index: 1000; /* Ensure it's above content but below close button */ `; elmnt.insertBefore(dragHandle, elmnt.firstChild); elmnt.style.overscrollBehavior = 'contain'; elmnt.style.webkitOverflowScrolling = 'touch'; elmnt.style.touchAction = 'pan-y'; const style = document.createElement('style'); style.textContent = ` #closeGUIButton { z-index: 1001; pointer-events: all !important; } .drag-handle { z-index: 1000; } @media (max-width: 767px) { #raceConfigGUI { -webkit-overflow-scrolling: touch !important; overflow-y: auto !important; touch-action: pan-y !important; overscroll-behavior-y: contain !important; } #raceConfigGUI::-webkit-scrollbar { width: 10px !important; } #raceConfigGUI::-webkit-scrollbar-thumb { background: #666 !important; border-radius: 5px !important; border: 2px solid #222 !important; } } `; document.head.appendChild(style); let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0; dragHandle.onmousedown = dragMouseDown; function dragMouseDown(e) { e = e || window.event; if (e.type === 'touchstart') return; e.preventDefault(); pos3 = e.clientX; pos4 = e.clientY; document.onmouseup = closeDragElement; document.onmousemove = elementDrag; } dragHandle.addEventListener('touchstart', function(e) { const touch = e.touches[0]; pos3 = touch.clientX; pos4 = touch.clientY; dragHandle.addEventListener('touchmove', handleTouchMove, { passive: false }); dragHandle.addEventListener('touchend', handleTouchEnd, { passive: true }); e.preventDefault(); }, { passive: false }); function handleTouchMove(e) { const touch = e.touches[0]; pos1 = pos3 - touch.clientX; pos2 = pos4 - touch.clientY; pos3 = touch.clientX; pos4 = touch.clientY; elmnt.style.top = (elmnt.offsetTop - pos2) + "px"; elmnt.style.left = (elmnt.offsetLeft - pos1) + "px"; e.preventDefault(); } function handleTouchEnd() { dragHandle.removeEventListener('touchmove', handleTouchMove); dragHandle.removeEventListener('touchend', handleTouchEnd); enforceWindowBoundaries(elmnt); } function elementDrag(e) { e = e || window.event; e.preventDefault(); pos1 = pos3 - e.clientX; pos2 = pos4 - e.clientY; pos3 = e.clientX; pos4 = e.clientY; let newTop = elmnt.offsetTop - pos2; let newLeft = elmnt.offsetLeft - pos1; const windowWidth = window.innerWidth; const windowHeight = window.innerHeight; const elmntWidth = elmnt.offsetWidth; const elmntHeight = elmnt.offsetHeight; const padding = 10; const minLeft = padding; const maxLeft = windowWidth - elmntWidth - padding; const minTop = padding; const maxTop = windowHeight - elmntHeight - padding; newLeft = Math.max(minLeft, Math.min(maxLeft, newLeft)); newTop = Math.max(minTop, Math.min(maxTop, newTop)); elmnt.style.top = newTop + "px"; elmnt.style.left = newLeft + "px"; } function closeDragElement() { document.onmouseup = null; document.onmousemove = null; enforceWindowBoundaries(elmnt); } function enforceWindowBoundaries(element) { const windowWidth = document.documentElement.clientWidth; const windowHeight = document.documentElement.clientHeight; const elmntWidth = element.offsetWidth; const elmntHeight = element.offsetHeight; const padding = 10; let { top, left } = element.getBoundingClientRect(); if (left < padding) element.style.left = padding + "px"; if (top < padding) element.style.top = padding + "px"; if (left + elmntWidth > windowWidth - padding) { element.style.left = (windowWidth - elmntWidth - padding) + "px"; } if (top + elmntHeight > windowHeight - padding) { element.style.top = (windowHeight - elmntHeight - padding) + "px"; } } window.addEventListener('resize', () => enforceWindowBoundaries(elmnt)); } function saveApiKey() { const apiKeyInput = document.getElementById('apiKeyInput'); if (!apiKeyInput) return; const apiKey = apiKeyInput.value.trim(); if (!apiKey) { displayStatusMessage('Please enter a valid API key', 'error'); return; } try { GM_setValue(STORAGE_API_KEY, apiKey); displayStatusMessage('API Key Saved', 'success'); setTimeout(() => displayStatusMessage('', ''), 3000); updateCarList(); } catch (e) { if (DEBUG) console.error('Error saving API key:', e); displayStatusMessage('Failed to save API key', 'error'); } } function loadApiKey() { const apiKeyInput = document.getElementById('apiKeyInput'); if (!apiKeyInput) return; try { const savedKey = GM_getValue(STORAGE_API_KEY, ''); apiKeyInput.value = savedKey || ''; } catch (e) { if (DEBUG) console.error('Error loading API key:', e); } } function displayStatusMessage(message, type = '', elementId = 'statusMessageBox') { const statusElement = document.getElementById(elementId); if (!statusElement) return; statusElement.textContent = message; statusElement.className = ''; if (type) { statusElement.classList.add(type); } statusElement.style.display = message ? 'block' : 'none'; if (DEBUG) console.log(`[Status - ${type}]: ${message}`); } function savePreset() { const carDropdown = document.getElementById('carDropdown'); const carId = document.getElementById('carIdInput').value; const raceName = document.getElementById('raceNameInput').value.trim(); if (!raceName) { displayStatusMessage('Please enter a race name before saving preset.', 'error'); setTimeout(() => displayStatusMessage('', ''), 3000); return; } if (!carId || carDropdown.value === '') { displayStatusMessage('Please select a car before creating a preset.', 'error'); setTimeout(() => displayStatusMessage('', ''), 3000); return; } const presetName = prompt("Enter a name for this preset:"); if (!presetName) { displayStatusMessage('Preset name cannot be empty.', 'error'); setTimeout(() => displayStatusMessage('', ''), 3000); return; } const carOption = carDropdown.querySelector(`option[value="${carId}"]`); const carName = carOption ? carOption.textContent.split(' [ID:')[0] : null; const saveTime = document.getElementById('saveTimeToPreset').checked; const presetData = { track: document.getElementById('trackSelect').value, laps: document.getElementById('lapsInput').value, minDrivers: document.getElementById('minDriversInput').value, maxDrivers: document.getElementById('maxDriversInput').value, raceName: document.getElementById('raceNameInput').value, password: document.getElementById('passwordInput').value, betAmount: document.getElementById('betAmountInput').value, hour: saveTime ? document.getElementById('hourSelect').value : null, minute: saveTime ? document.getElementById('minuteSelect').value : null, carId: carId, carName: carName, selectedCar: carDropdown.value, saveTime: saveTime }; let presets = loadPresets(); presets[presetName] = presetData; set_value('race_presets', presets); displayPresets(); updateQuickPresetsDisplay(); updateQuickLaunchButtons(); displayStatusMessage(`Preset "${presetName}" saved.`, 'success'); setTimeout(() => displayStatusMessage('', ''), 3000); } function getNextAvailableTime(hour, minute) { if (!hour || !minute) return null; const now = moment.utc(); let targetTime = moment.utc().set({ hour: parseInt(hour), minute: parseInt(minute), second: 0, millisecond: 0 }); if (targetTime.isSameOrBefore(now)) { targetTime = targetTime.add(1, 'day'); } return targetTime; } function applyPreset(presetName) { const presets = loadPresets(); const preset = presets[presetName]; if (preset) { const trackSelect = document.getElementById('trackSelect'); const lapsInput = document.getElementById('lapsInput'); const minDriversInput = document.getElementById('minDriversInput'); const maxDriversInput = document.getElementById('maxDriversInput'); const raceNameInput = document.getElementById('raceNameInput'); const passwordInput = document.getElementById('passwordInput'); const betAmountInput = document.getElementById('betAmountInput'); const hourSelect = document.getElementById('hourSelect'); const minuteSelect = document.getElementById('minuteSelect'); const carDropdown = document.getElementById('carDropdown'); const carIdInput = document.getElementById('carIdInput'); if (trackSelect) trackSelect.value = preset.track; if (lapsInput) lapsInput.value = preset.laps; if (minDriversInput) minDriversInput.value = preset.minDrivers; if (maxDriversInput) maxDriversInput.value = preset.maxDrivers; if (raceNameInput) raceNameInput.value = preset.raceName; if (passwordInput) passwordInput.value = preset.password; if (betAmountInput) betAmountInput.value = preset.betAmount; if (preset.saveTime && preset.hour && preset.minute) { const nextTime = getNextAvailableTime(preset.hour, preset.minute); if (nextTime) { if (hourSelect) hourSelect.value = preset.hour; if (minuteSelect) minuteSelect.value = preset.minute; } } else { if (hourSelect) hourSelect.value = '00'; if (minuteSelect) minuteSelect.value = '00'; } document.getElementById('saveTimeToPreset').checked = preset.saveTime || false; if (carDropdown && preset.selectedCar) { carDropdown.value = preset.selectedCar; } if (carIdInput) { carIdInput.value = preset.carId || preset.selectedCar || ''; } displayStatusMessage(`Preset "${presetName}" applied.`, 'success'); setTimeout(() => displayStatusMessage('', ''), 3000); } else { displayStatusMessage(`Preset "${presetName}" not found.`, 'error'); setTimeout(() => displayStatusMessage('', ''), 3000); } } function loadPresets() { return get_value('race_presets') || {}; } function loadAllPresets() { return loadPresets() || {}; } function displayPresets() { const presets = loadPresets(); const container = document.getElementById('presetButtonsContainer'); if (!container) return; container.innerHTML = ''; if (Object.keys(presets).length === 0) { container.textContent = 'No presets saved yet.'; return; } Object.keys(presets).forEach(presetName => { const preset = presets[presetName]; const presetButtonContainer = document.createElement('div'); presetButtonContainer.className = 'preset-button-container'; const presetButton = document.createElement('button'); presetButton.className = 'preset-button'; const carName = preset.carName || 'Unknown Car'; presetButton.innerHTML = ` <div class="preset-title">${presetName}</div> <div class="preset-info"> ${trackNames[preset.track] || 'Unknown Track'}<br> Laps: ${preset.laps}<br> ${carName} </div> `; presetButton.title = `Apply preset: ${presetName}`; presetButton.addEventListener('click', () => applyPreset(presetName)); presetButtonContainer.appendChild(presetButton); const removeButton = document.createElement('a'); removeButton.className = 'remove-preset'; removeButton.href = '#'; removeButton.textContent = '×'; removeButton.title = `Remove preset: ${presetName}`; removeButton.addEventListener('click', (event) => { event.preventDefault(); removePreset(presetName); }); presetButtonContainer.appendChild(removeButton); container.appendChild(presetButtonContainer); }); } function removePreset(presetName) { if (!confirm(`Are you sure you want to remove preset "${presetName}"?`)) { return; } let presets = loadPresets(); delete presets[presetName]; set_value('race_presets', presets); displayPresets(); updateQuickPresetsDisplay(); updateQuickLaunchButtons(); displayStatusMessage(`Preset "${presetName}" removed.`, 'success'); setTimeout(() => displayStatusMessage('', ''), 3000); } function clearPresets() { if (confirm("Are you sure you want to clear ALL saved presets?")) { set_value('race_presets', {}); displayPresets(); updateQuickPresetsDisplay(); updateQuickLaunchButtons(); displayStatusMessage('All presets cleared.', 'success'); setTimeout(() => displayStatusMessage('', ''), 3000); } } function updateQuickPresetsDisplay() { const quickPresetsContainer = document.getElementById('quickPresetButtonsContainer'); if (!quickPresetsContainer) return; quickPresetsContainer.innerHTML = ''; // This function is now empty as quick presets were part of the removed features, // but keeping the shell in case it's repurposed later. } function updateQuickLaunchButtons() { const container = document.getElementById('quickLaunchContainer'); if (!container) return; container.innerHTML = ''; const presets = loadPresets(); if (Object.keys(presets).length === 0) { container.style.display = 'none'; return; } if (DEBUG) console.log('Creating minimize button'); const minimizeButtonWrapper = document.createElement('div'); minimizeButtonWrapper.id = 'minimizeQuickLaunchButtonWrapper'; minimizeButtonWrapper.style.cssText = ` position: absolute !important; top: 2px !important; right: 2px !important; width: 30px !important; height: 30px !important; z-index: 1000001 !important; cursor: pointer !important; pointer-events: auto !important; `; const minimizeButton = document.createElement('button'); minimizeButton.id = 'minimizeQuickLaunchButton'; minimizeButton.type = 'button'; minimizeButton.title = 'Minimize Quick Launch Area'; minimizeButton.style.cssText = ` width: 100% !important; height: 100% !important; background-color: #444 !important; color: white !important; border: 1px solid #666 !important; border-radius: 4px !important; font-size: 16px !important; cursor: pointer !important; z-index: 1000000 !important; display: flex !important; align-items: center !important; justify-content: center !important; transition: all 0.2s ease !important; box-shadow: 0 1px 3px rgba(0,0,0,0.3) !important; pointer-events: auto !important; `; const innerContent = document.createElement('div'); innerContent.id = 'minimizeQuickLaunchButtonContent'; innerContent.textContent = '_'; innerContent.style.cssText = ` pointer-events: none !important; width: 100% !important; height: 100% !important; display: flex !important; align-items: center !important; justify-content: center !important; `; minimizeButton.appendChild(innerContent); minimizeButtonWrapper.appendChild(minimizeButton); container.appendChild(minimizeButtonWrapper); if (DEBUG) console.log('Minimize button added to container'); const buttonContainer = document.createElement('div'); buttonContainer.className = 'button-container'; const statusDiv = document.createElement('div'); statusDiv.className = 'quick-launch-status'; const quickLaunchHeader = document.createElement('div'); quickLaunchHeader.className = 'preset-section-header'; quickLaunchHeader.textContent = 'Quick Launch Presets'; container.appendChild(quickLaunchHeader); container.appendChild(buttonContainer); container.appendChild(statusDiv); Object.entries(presets).forEach(([name, preset]) => { const button = document.createElement('button'); button.className = 'quick-launch-button'; button.textContent = name; const carName = preset.carName || `Car ID: ${preset.carId}`; const tooltipInfo = [ `${name}`, `Track: ${trackNames[preset.track] || 'Unknown Track'}`, `Car: ${carName}`, `Laps: ${preset.laps}`, `Drivers: ${preset.minDrivers}-${preset.maxDrivers}`, `Password: ${preset.password ? 'Yes' : 'No'}`, preset.betAmount > 0 ? `Bet: $${Number(preset.betAmount).toLocaleString()}` : null ].filter(Boolean).join('\n'); button.title = tooltipInfo; button.addEventListener('click', async () => { await createRaceFromPreset(preset); }); buttonContainer.appendChild(button); }); container.style.display = 'flex'; const addButtonListeners = () => { const btnWrapper = document.getElementById('minimizeQuickLaunchButtonWrapper'); if (!btnWrapper) { if (DEBUG) console.error('Could not find minimize button or wrapper'); return; } const newWrapper = btnWrapper.cloneNode(true); if (btnWrapper.parentNode) { btnWrapper.parentNode.replaceChild(newWrapper, btnWrapper); } newWrapper.addEventListener('click', function(e) { if (DEBUG) console.log('Minimize button wrapper clicked'); e.preventDefault(); e.stopPropagation(); toggleQuickLaunchMinimize(); }); }; addButtonListeners(); setTimeout(addButtonListeners, 100); const minimizedState = GM_getValue('quickLaunchMinimized', false); if (minimizedState === true) { container.classList.add('minimized'); const btnContent = document.getElementById('minimizeQuickLaunchButtonContent'); if (btnContent) btnContent.textContent = '□'; const minimizeBtn = document.getElementById('minimizeQuickLaunchButton'); if (minimizeBtn) minimizeBtn.title = 'Expand Quick Launch Area'; container.style.cssText += "max-height: 35px !important; overflow: hidden !important;"; const buttonContainerElem = container.querySelector('.button-container'); if (buttonContainerElem) buttonContainerElem.style.display = 'none'; } } function toggleQuickLaunchMinimize() { if (DEBUG) console.log('toggleQuickLaunchMinimize called'); const container = document.getElementById('quickLaunchContainer'); const minimizeButton = document.getElementById('minimizeQuickLaunchButton'); const buttonContent = document.getElementById('minimizeQuickLaunchButtonContent'); if (!container) { if (DEBUG) console.error('Container not found'); return; } const isCurrentlyMinimized = container.classList.contains('minimized'); if (DEBUG) console.log('Current state:', isCurrentlyMinimized ? 'minimized' : 'maximized'); if (isCurrentlyMinimized) { container.classList.remove('minimized'); if (buttonContent) buttonContent.textContent = '_'; if (minimizeButton) minimizeButton.title = 'Minimize Quick Launch Area'; GM_setValue('quickLaunchMinimized', false); container.style.maxHeight = 'none'; container.style.overflow = 'visible'; if (DEBUG) console.log('Container maximized'); } else { container.classList.add('minimized'); if (buttonContent) buttonContent.textContent = '□'; if (minimizeButton) minimizeButton.title = 'Expand Quick Launch Area'; GM_setValue('quickLaunchMinimized', true); container.style.maxHeight = '35px'; container.style.overflow = 'hidden'; if (DEBUG) console.log('Container minimized'); } } function displayQuickLaunchStatus(message, type = '') { const statusElement = document.querySelector('.quick-launch-status'); if (!statusElement) return; statusElement.textContent = message; statusElement.className = 'quick-launch-status'; if (type) { statusElement.classList.add(type); statusElement.classList.add('show'); } if (message && message.trim() !== '') { const container = document.getElementById('quickLaunchContainer'); if (container && container.classList.contains('minimized')) { statusElement.style.display = 'block'; setTimeout(() => { const statusHeight = statusElement.scrollHeight || 20; container.style.maxHeight = `${Math.max(35, statusHeight + 40)}px`; }, 10); } } displayStatusMessage(message, type); } function populateTimeDropdowns() { const hourSelect = document.getElementById('hourSelect'); const minuteSelect = document.getElementById('minuteSelect'); if (!hourSelect || !minuteSelect) return; for (let i = 0; i <= 23; i++) { const option = document.createElement('option'); option.value = String(i).padStart(2, '0'); option.textContent = String(i).padStart(2, '0'); hourSelect.appendChild(option); } const minutes = ['00', '15', '30', '45']; minutes.forEach(minute => { const option = document.createElement('option'); option.value = minute; option.textContent = minute; minuteSelect.appendChild(option); }); } function setTimeToNow() { const hourSelect = document.getElementById('hourSelect'); const minuteSelect = document.getElementById('minuteSelect'); if (!hourSelect || !minuteSelect) return; const now = moment.utc(); const currentHour = now.hour(); const currentMinute = now.minute(); hourSelect.value = String(currentHour).padStart(2, '0'); let roundedMinute = Math.round(currentMinute / 15) * 15; if (roundedMinute === 60) { roundedMinute = 0; } const tempOption = minuteSelect.querySelector('.temp-minute'); if (tempOption) { tempOption.remove(); } if (![0, 15, 30, 45].includes(currentMinute)) { const option = document.createElement('option'); option.value = String(currentMinute).padStart(2, '0'); option.textContent = String(currentMinute).padStart(2, '0'); option.className = 'temp-minute'; minuteSelect.appendChild(option); minuteSelect.value = String(currentMinute).padStart(2, '0'); } else { minuteSelect.value = String(roundedMinute).padStart(2, '0'); } } async function updateCarList() { const carDropdown = document.getElementById('carDropdown'); const carStatusMessage = document.getElementById('carStatusMessage'); const updateCarsButton = document.getElementById('updateCarsButton'); const apiKey = GM_getValue(STORAGE_API_KEY, ''); if (!apiKey) { if (carStatusMessage) { carStatusMessage.textContent = 'API Key Required'; carStatusMessage.style.color = 'red'; } return; } if (carStatusMessage) { carStatusMessage.textContent = 'Updating Cars...'; carStatusMessage.style.color = '#aaa'; } if (carDropdown) carDropdown.disabled = true; if (updateCarsButton) updateCarsButton.disabled = true; try { const response = await GM.xmlHttpRequest({ url: `https://api.torn.com/user/?selections=racing,cars&key=${apiKey}`, method: 'GET', headers: { 'Content-Type': 'application/json' }, onload: function(response) { try { if (response.status === 200) { const data = JSON.parse(response.responseText); if (data.error) { if (carStatusMessage) { carStatusMessage.textContent = `API Error: ${data.error.error}`; carStatusMessage.style.color = 'red'; } } else if (data.cars) { populateCarDropdown(data.cars); if (carStatusMessage) { carStatusMessage.textContent = 'Cars Updated'; carStatusMessage.style.color = '#efe'; } } else { if (carStatusMessage) { carStatusMessage.textContent = 'No car data received'; carStatusMessage.style.color = 'orange'; } } } else { if (carStatusMessage) { carStatusMessage.textContent = `HTTP Error: ${response.status}`; carStatusMessage.style.color = 'red'; } } } catch (e) { if (DEBUG) console.error('Error parsing response:', e); if (carStatusMessage) { carStatusMessage.textContent = 'Error parsing car data'; carStatusMessage.style.color = 'red'; } } finally { if (carDropdown) carDropdown.disabled = false; if (updateCarsButton) updateCarsButton.disabled = false; if (carStatusMessage) { setTimeout(() => { if (carStatusMessage) carStatusMessage.textContent = ''; }, 3000); } } }, onerror: function(error) { if (DEBUG) console.error('Request failed:', error); if (carStatusMessage) { carStatusMessage.textContent = 'Request failed'; carStatusMessage.style.color = 'red'; } if (carDropdown) carDropdown.disabled = false; if (updateCarsButton) updateCarsButton.disabled = false; if (carStatusMessage) { setTimeout(() => { if (carStatusMessage) carStatusMessage.textContent = ''; }, 5000); } } }); } catch (error) { if (DEBUG) console.error('Error updating cars:', error); if (carStatusMessage) { carStatusMessage.textContent = `Error: ${error.message}`; carStatusMessage.style.color = 'red'; } if (carDropdown) carDropdown.disabled = false; if (updateCarsButton) updateCarsButton.disabled = false; if (carStatusMessage) { setTimeout(() => { if (carStatusMessage) carStatusMessage.textContent = ''; }, 5000); } } } function populateCarDropdown(cars) { if (DEBUG) console.log('[DEBUG] Populating car dropdown with', Object.keys(cars).length, 'cars'); const dropdown = document.getElementById('carDropdown'); if (!dropdown) { if (DEBUG) console.log('[DEBUG] Dropdown not found in populateCarDropdown'); return; } const sortedCars = Object.values(cars).sort((a, b) => (a.name || '').localeCompare(b.name || '')); dropdown.innerHTML = '<option value="">Select a car...</option>'; sortedCars.forEach(car => { const carName = car.name || `Car #${car.ID}`; const option = document.createElement('option'); option.value = car.ID; option.textContent = `${carName} [ID: ${car.ID}]`; dropdown.appendChild(option); }); if (DEBUG) console.log('[DEBUG] Added', sortedCars.length, 'cars to dropdown', dropdown.id); } function updateCarDropdown() { updateCarList(); } function getRFC() { const rfcFromPage = getRFCFromPage(); if (rfcFromPage) { if (DEBUG) console.log('[RFC] Found from page:', rfcFromPage); return rfcFromPage; } if (typeof $ !== 'undefined' && typeof $.cookie === 'function') { const rfcValue = $.cookie('rfc_v'); if (rfcValue) { if (DEBUG) console.log('[RFC] Found from jQuery cookie:', rfcValue); return rfcValue; } } const cookies = document.cookie.split("; "); for (let i in cookies) { const cookie = cookies[i].split("="); if (cookie[0] && cookie[0].trim() === "rfc_v") { if (DEBUG) console.log('[RFC] Found from document.cookie:', cookie[1]); return cookie[1]; } } if (DEBUG) console.warn("[RFC] Failed to find rfc_v cookie or value."); return ''; } function getRFCFromPage() { const scripts = document.querySelectorAll('script'); for (const script of scripts) { if (!script.textContent) continue; const rfcMatch = script.textContent.match(/var\s+rfcv\s*=\s*['"]([^'"]+)['"]/); if (rfcMatch && rfcMatch[1]) { if (DEBUG) console.log('[RFC Detection] Found RFC in script tag:', rfcMatch[1]); return rfcMatch[1]; } } return null; } async function createRace() { const apiKey = GM_getValue(STORAGE_API_KEY, ''); const raceName = document.getElementById('raceNameInput').value.trim(); if (!apiKey) { displayStatusMessage('API Key is required to create race.', 'error'); setTimeout(() => displayStatusMessage('', ''), 3000); return; } if (!raceName) { displayStatusMessage('Please enter a race name.', 'error'); setTimeout(() => displayStatusMessage('', ''), 3000); return; } const trackId = document.getElementById('trackSelect').value; const laps = document.getElementById('lapsInput').value; const minDrivers = document.getElementById('minDriversInput').value; const maxDrivers = document.getElementById('maxDriversInput').value; const password = document.getElementById('passwordInput').value; const betAmount = document.getElementById('betAmountInput').value; const carId = document.getElementById('carIdInput').value; const waitTime = Math.floor(Date.now() / 1000); const rfcValue = getRFC(); const params = new URLSearchParams(); params.append('carID', carId); params.append('password', password); params.append('createRace', 'true'); params.append('title', raceName); params.append('minDrivers', minDrivers); params.append('maxDrivers', maxDrivers); params.append('trackID', trackId); params.append('laps', laps); params.append('minClass', '5'); params.append('carsTypeAllowed', '1'); params.append('carsAllowed', '5'); params.append('betAmount', betAmount); params.append('waitTime', waitTime); params.append('rfcv', rfcValue); const raceLink = `https://www.torn.com/loader.php?sid=racing&tab=customrace§ion=getInRace&step=getInRace&id=&${params.toString()}`; if (DEBUG) console.log('[Race URL]:', raceLink); displayStatusMessage('Creating Race...', 'info'); try { const response = await fetch(raceLink); const data = await response.text(); if (data.includes('success') || response.ok) { displayStatusMessage('Race Created Successfully!', 'success'); setTimeout(() => window.location.href = 'https://www.torn.com/loader.php?sid=racing', 1500); } else { displayStatusMessage('Error creating race. Please try again.', 'error'); } setTimeout(() => displayStatusMessage('', ''), 3000); } catch (error) { displayStatusMessage(`Error creating race: ${error.message}`, 'error'); setTimeout(() => displayStatusMessage('', ''), 5000); } } async function createRaceFromPreset(preset) { const apiKey = GM_getValue(STORAGE_API_KEY, ''); if (!apiKey) { displayQuickLaunchStatus('API Key is required to create race.', 'error'); return; } const trackId = preset.track; const laps = preset.laps; const minDrivers = preset.minDrivers; const maxDrivers = preset.maxDrivers; const raceName = preset.raceName; const password = preset.password; const betAmount = preset.betAmount; const carId = preset.carId; const waitTime = Math.floor(Date.now() / 1000); const rfcValue = getRFC(); const params = new URLSearchParams(); params.append('carID', carId); params.append('password', password); params.append('createRace', 'true'); params.append('title', raceName); params.append('minDrivers', minDrivers); params.append('maxDrivers', maxDrivers); params.append('trackID', trackId); params.append('laps', laps); params.append('minClass', '5'); params.append('carsTypeAllowed', '1'); params.append('carsAllowed', '5'); params.append('betAmount', betAmount); params.append('waitTime', waitTime); params.append('rfcv', rfcValue); const raceLink = `https://www.torn.com/loader.php?sid=racing&tab=customrace§ion=getInRace&step=getInRace&id=&${params.toString()}`; if (DEBUG) console.log('[Race URL from preset]:', raceLink); displayQuickLaunchStatus('Creating Race...', 'info'); try { const response = await fetch(raceLink); const data = await response.text(); if (data.includes('success') || response.ok) { displayQuickLaunchStatus('Race Created Successfully!', 'success'); setTimeout(() => window.location.reload(), 1500); } else { displayQuickLaunchStatus('Error creating race. Please try again.', 'error'); } } catch (error) { displayQuickLaunchStatus(`Error creating race: ${error.message}`, 'error'); } } function set_value(key, value) { try { if (key === STORAGE_API_KEY) { GM_setValue(key, value); } else { GM_setValue(key, JSON.stringify(value)); } } catch (e) { if (DEBUG) console.error('Error saving value:', e); } } function get_value(key, defaultValue) { try { if (key === STORAGE_API_KEY) { return GM_getValue(key, defaultValue); } const value = GM_getValue(key); return value ? JSON.parse(value) : defaultValue; } catch (e) { if (DEBUG) console.error('Error reading value:', e); return defaultValue; } } function initializeRaceFiltering() { if (DEBUG) console.log('[DEBUG] Starting race filtering initialization'); if (window.raceFilteringInitialized) { if (DEBUG) console.log('[DEBUG] Race filtering already initialized'); return; } window.RaceFiltering = window.RaceFiltering || { filterRacesList() { if (DEBUG) console.log('[DEBUG] Applying filters to race list'); const filters = { track: document.getElementById('filterTrack')?.value || '', laps: { min: parseInt(document.getElementById('filterMinLaps')?.value) || null, max: parseInt(document.getElementById('filterMaxLaps')?.value) || null }, drivers: { min: parseInt(document.getElementById('filterMinDrivers')?.value) || null, max: parseInt(document.getElementById('filterMaxDrivers')?.value) || null }, sortBy: document.getElementById('filterSort')?.value || 'time', hidePassworded: document.getElementById('hidePassworded')?.checked || false, showSuitableCarsOnly: document.getElementById('showSuitableCarsOnly')?.checked || false, hideFullRaces: document.getElementById('hideFullRaces')?.checked || false }; const racesList = document.querySelector('.custom_events, .events-list, .races-list'); if (!racesList) return; const races = Array.from(racesList.children); races.forEach(race => { let shouldShow = true; if (filters.track && !this.matchesTrackFilter(race, filters.track)) { shouldShow = false; } if (shouldShow && filters.showSuitableCarsOnly && !this.hasSuitableCar(race)) { shouldShow = false; } if (shouldShow && filters.laps.min && !this.matchesMinLapsFilter(race, filters.laps.min)) { shouldShow = false; } if (shouldShow && filters.laps.max && !this.matchesMaxLapsFilter(race, filters.laps.max)) { shouldShow = false; } if (shouldShow && filters.drivers.min && !this.matchesMinDriversFilter(race, filters.drivers.min)) { shouldShow = false; } if (shouldShow && filters.drivers.max && !this.matchesMaxDriversFilter(race, filters.drivers.max)) { shouldShow = false; } if (shouldShow && filters.hidePassworded && this.isPasswordProtected(race)) { shouldShow = false; } if (shouldShow && filters.hideFullRaces && this.isRaceFull(race)) { shouldShow = false; } race.style.display = shouldShow ? '' : 'none'; }); const visibleRaces = races.filter(race => race.style.display !== 'none'); visibleRaces.sort((a, b) => { switch (filters.sortBy) { case 'time': return this.compareTime(a, b); case 'track': return this.compareTrack(a, b); case 'laps': return this.compareLaps(a, b); case 'bets': return this.compareBets(a, b); case 'drivers': return this.compareDrivers(a, b); default: return 0; } }); visibleRaces.forEach(race => racesList.appendChild(race)); }, matchesTrackFilter(race, trackName) { if (!trackName) return true; const trackElement = race.querySelector('li.track'); if (!trackElement) return true; const trackText = trackElement.textContent.replace(/\(\d+\s*laps?\)/i, '').trim(); return trackText.toLowerCase() === trackName.toLowerCase(); }, hasSuitableCar(race) { const notSuitableText = "You do not have a suitable car enlisted for this race."; return !race.textContent.includes(notSuitableText); }, matchesMinLapsFilter(race, minLaps) { if (!minLaps) return true; const lapsElement = race.querySelector('li.track span.laps'); if (!lapsElement) return true; const lapsMatch = lapsElement.textContent.match(/(\d+)\s*laps?/i); const raceLaps = lapsMatch ? parseInt(lapsMatch[1]) : 0; return raceLaps >= minLaps; }, matchesMaxLapsFilter(race, maxLaps) { if (!maxLaps) return true; const lapsElement = race.querySelector('li.track span.laps'); if (!lapsElement) return true; const lapsMatch = lapsElement.textContent.match(/(\d+)\s*laps?/i); const raceLaps = lapsMatch ? parseInt(lapsMatch[1]) : 0; return raceLaps <= maxLaps; }, matchesMinDriversFilter(race, minDrivers) { if (!minDrivers) return true; const driversElement = race.querySelector('li.drivers'); if (!driversElement) return true; const driversMatch = driversElement.textContent.match(/(\d+)\s*\/\s*(\d+)/); if (!driversMatch) return true; const maxRaceDrivers = parseInt(driversMatch[2]); return maxRaceDrivers >= minDrivers; }, matchesMaxDriversFilter(race, maxDrivers) { if (!maxDrivers) return true; const driversElement = race.querySelector('li.drivers'); if (!driversElement) return true; const driversMatch = driversElement.textContent.match(/(\d+)\s*\/\s*(\d+)/); if (!driversMatch) return true; const maxRaceDrivers = parseInt(driversMatch[2]); return maxRaceDrivers <= maxDrivers; }, isPasswordProtected(race) { const raceText = race.textContent.toLowerCase(); return raceText.includes('password') || race.querySelector('[id^="joinPasswordForm"]') !== null; }, isRaceFull(race) { const driversElement = race.querySelector('li.drivers'); if (!driversElement) return false; const driversMatch = driversElement.textContent.match(/(\d+)\s*\/\s*(\d+)/); if (!driversMatch) return false; const currentDrivers = parseInt(driversMatch[1]); const maxDrivers = parseInt(driversMatch[2]); return currentDrivers >= maxDrivers; }, compareTime(a, b) { const getTimeInMinutes = el => { const timeText = (el.textContent || '').toLowerCase(); if (timeText.includes('waiting')) return -1; let totalMinutes = 0; const fullMatch = timeText.match(/(\d+)\s*h(?:our)?s?\s*(?:and)?\s*(\d+)\s*(?:min(?:ute)?s?|m)/); if (fullMatch) return (parseInt(fullMatch[1]) * 60) + parseInt(fullMatch[2]); const hoursOnlyMatch = timeText.match(/(\d+)\s*h(?:our)?s?/); if (hoursOnlyMatch) return parseInt(hoursOnlyMatch[1]) * 60; const minutesOnlyMatch = timeText.match(/(\d+)\s*(?:min(?:ute)?s?|m)/); if (minutesOnlyMatch) return parseInt(minutesOnlyMatch[1]); const timeMatch = timeText.match(/(\d+):(\d+)/); if (timeMatch) return (parseInt(timeMatch[1]) * 60) + parseInt(timeMatch[2]); return Infinity; }; const timeA = getTimeInMinutes(a); const timeB = getTimeInMinutes(b); return timeA - timeB; }, compareTrack(a, b) { const getTrackName = el => { const trackElement = el.querySelector('li.track'); if (!trackElement) return ''; return trackElement.textContent.replace(/\(\d+\s*laps?\)/i, '').trim(); }; const trackA = getTrackName(a).toLowerCase(); const trackB = getTrackName(b).toLowerCase(); return trackA.localeCompare(trackB); }, compareLaps(a, b) { const getLaps = el => { const lapsMatch = el.textContent.match(/(\d+)\s*laps?/i); return parseInt(lapsMatch?.[1]) || 0; }; return getLaps(a) - getLaps(b); }, compareBets(a, b) { const getBet = el => { const betMatch = el.textContent.match(/\$([0-9,]+)/); return parseInt(betMatch?.[1]?.replace(/,/g, '')) || 0; }; return getBet(b) - getBet(a); }, compareDrivers(a, b) { const getMaxDrivers = el => { const driversElement = el.querySelector('li.drivers'); if (!driversElement) return 0; const driversMatch = driversElement.textContent.match(/(\d+)\s*\/\s*(\d+)/); if (!driversMatch) return 0; return parseInt(driversMatch[2]) || 0; }; return getMaxDrivers(a) - getMaxDrivers(b); } }; if (!window.location.href.includes('sid=racing')) { return; } window.raceFilteringInitialized = true; if (window.raceFilterObserver) { window.raceFilterObserver.disconnect(); } let debounceTimer; window.raceFilterObserver = new MutationObserver((mutations) => { clearTimeout(debounceTimer); debounceTimer = setTimeout(() => { const raceList = document.querySelector('.custom_events, .events-list, .races-list'); const filterSection = document.querySelector('.race-filter-section'); if (raceList && !filterSection) { if (DEBUG) console.log('[DEBUG] Race list found, setting up filters'); setupFilterControls(); window.RaceFiltering.filterRacesList(); } }, 100); }); const observerConfig = { childList: true, subtree: true }; const container = document.getElementById('racingMainContainer') || document.body; window.raceFilterObserver.observe(container, observerConfig); const raceList = document.querySelector('.custom_events, .events-list, .races-list'); if (raceList && !raceList.querySelector('.race-filter-section')) { setupFilterControls(); window.RaceFiltering.filterRacesList(); } window.addEventListener('unload', () => { if (window.raceFilterObserver) { window.raceFilterObserver.disconnect(); } window.raceFilteringInitialized = false; }); } function refreshRacesList() { if (DEBUG) console.log('[DEBUG] Refreshing race list'); const customEventsTab = document.querySelector('a[href*="tab=customrace"]'); if (customEventsTab) { const observer = new MutationObserver((mutations, obs) => { const racesList = document.querySelector('.custom_events, .events-list, .races-list'); if (racesList) { if (DEBUG) console.log('[DEBUG] Race list detected after refresh'); setTimeout(() => { if (DEBUG) console.log('[DEBUG] Restoring filters and reapplying'); const filtersEnabled = restoreFilterState(); if (filtersEnabled && document.getElementById('toggleFilters')?.classList.contains('active')) { window.RaceFiltering?.filterRacesList(); } obs.disconnect(); }, 500); } }); const container = document.getElementById('racingMainContainer') || document.body; observer.observe(container, { childList: true, subtree: true }); customEventsTab.click(); setTimeout(() => { observer.disconnect(); if (DEBUG) console.log('[DEBUG] Observer timed out - no race list found'); }, 10000); } } function clearFilters() { const filterTrack = document.getElementById('filterTrack'); const filterMinLaps = document.getElementById('filterMinLaps'); const filterMaxLaps = document.getElementById('filterMaxLaps'); const filterMinDrivers = document.getElementById('filterMinDrivers'); const filterMaxDrivers = document.getElementById('filterMaxDrivers'); const filterSort = document.getElementById('filterSort'); const hidePassworded = document.getElementById('hidePassworded'); const showSuitableCarsOnly = document.getElementById('showSuitableCarsOnly'); const hideFullRaces = document.getElementById('hideFullRaces'); if (filterTrack) filterTrack.value = ''; if (filterMinLaps) filterMinLaps.value = ''; if (filterMaxLaps) filterMaxLaps.value = ''; if (filterMinDrivers) filterMinDrivers.value = ''; if (filterMaxDrivers) filterMaxDrivers.value = ''; if (filterSort) filterSort.value = 'time'; if (hidePassworded) hidePassworded.checked = false; if (showSuitableCarsOnly) showSuitableCarsOnly.checked = false; if (hideFullRaces) hideFullRaces.checked = false; const racesList = document.querySelector('.custom_events, .events-list, .races-list'); if (racesList) { Array.from(racesList.children).forEach(race => { race.style.display = ''; }); } } function saveFilterState() { const state = { track: document.getElementById('filterTrack')?.value || '', minLaps: document.getElementById('filterMinLaps')?.value || '', maxLaps: document.getElementById('filterMaxLaps')?.value || '', minDrivers: document.getElementById('filterMinDrivers')?.value || '', maxDrivers: document.getElementById('filterMaxDrivers')?.value || '', sortBy: document.getElementById('filterSort')?.value || 'time', hidePassworded: document.getElementById('hidePassworded')?.checked || false, showSuitableCarsOnly: document.getElementById('showSuitableCarsOnly')?.checked || false, hideFullRaces: document.getElementById('hideFullRaces')?.checked || false, filtersEnabled: document.getElementById('toggleFilters')?.classList.contains('active') || false }; GM_setValue('raceFilterState', JSON.stringify(state)); } function restoreFilterState() { try { const savedState = JSON.parse(GM_getValue('raceFilterState', '{}')); const filterTrack = document.getElementById('filterTrack'); const filterMinLaps = document.getElementById('filterMinLaps'); const filterMaxLaps = document.getElementById('filterMaxLaps'); const filterMinDrivers = document.getElementById('filterMinDrivers'); const filterMaxDrivers = document.getElementById('filterMaxDrivers'); const filterSort = document.getElementById('filterSort'); const hidePassworded = document.getElementById('hidePassworded'); const showSuitableCarsOnly = document.getElementById('showSuitableCarsOnly'); const hideFullRaces = document.getElementById('hideFullRaces'); const toggleFilters = document.getElementById('toggleFilters'); if (filterTrack) filterTrack.value = savedState.track || ''; if (filterMinLaps) filterMinLaps.value = savedState.minLaps || ''; if (filterMaxLaps) filterMaxLaps.value = savedState.maxLaps || ''; if (filterMinDrivers) filterMinDrivers.value = savedState.minDrivers || ''; if (filterMaxDrivers) filterMaxDrivers.value = savedState.maxDrivers || ''; if (filterSort) filterSort.value = savedState.sortBy || 'time'; if (hidePassworded) hidePassworded.checked = savedState.hidePassworded || false; if (showSuitableCarsOnly) showSuitableCarsOnly.checked = savedState.showSuitableCarsOnly || false; if (hideFullRaces) hideFullRaces.checked = savedState.hideFullRaces || false; if (toggleFilters) { if (savedState.filtersEnabled) { toggleFilters.classList.add('active'); toggleFilters.textContent = 'Disable Filters'; } else { toggleFilters.classList.remove('active'); toggleFilters.textContent = 'Enable Filters'; } } return savedState.filtersEnabled; } catch (e) { if (DEBUG) console.error('Error restoring filter state:', e); return false; } } function setupFilterControls() { const raceList = document.querySelector('.custom_events, .events-list, .races-list'); if (!raceList || raceList.querySelector('.race-filter-section')) { return; } const existingFilters = document.querySelectorAll('.race-filter-section'); existingFilters.forEach(el => el.remove()); if (DEBUG) console.log('[DEBUG] Creating filter controls'); const filterContainer = document.createElement('div'); filterContainer.className = 'race-filter-section'; filterContainer.innerHTML = ` <div class="race-filter-controls"> <div class="filter-row"> <div class="filter-group"> <label>Track:</label> <select id="filterTrack"> <option value="">All Tracks</option> <option value="Uptown">Uptown</option> <option value="Withdrawal">Withdrawal</option> <option value="Underdog">Underdog</option> <option value="Parkland">Parkland</option> <option value="Docks">Docks</option> <option value="Commerce">Commerce</option> <option value="Two Islands">Two Islands</option> <option value="Industrial">Industrial</option> <option value="Vector">Vector</option> <option value="Mudpit">Mudpit</option> <option value="Hammerhead">Hammerhead</option> <option value="Sewage">Sewage</option> <option value="Meltdown">Meltdown</option> <option value="Speedway">Speedway</option> <option value="Stone Park">Stone Park</option> <option value="Convict">Convict</option> </select> </div> <div class="filter-group laps-filter"> <label>Laps:</label> <input type="number" id="filterMinLaps" placeholder="Min" min="1" max="100"> <span>-</span> <input type="number" id="filterMaxLaps" placeholder="Max" min="1" max="100"> </div> </div> <div class="filter-row"> <div class="filter-group laps-filter"> <label>Drivers:</label> <input type="number" id="filterMinDrivers" placeholder="Min" min="2" max="10"> <span>-</span> <input type="number" id="filterMaxDrivers" placeholder="Max" min="2" max="10"> </div> <div class="filter-group" style="margin-left: 15px;"> <label>Sort By:</label> <select id="filterSort"> <option value="time">Start Time</option> <option value="track">Track</option> <option value="laps">Laps</option> <option value="bets">Bet Amount</option> <option value="drivers">Drivers</option> </select> </div> </div> <div class="filter-row"> <div class="filter-group checkboxes"> <div class="checkbox-option"> <label><input type="checkbox" id="hidePassworded"> Hide Passworded</label> </div> <div class="checkbox-option"> <label><input type="checkbox" id="showSuitableCarsOnly"> Show Suitable Cars Only</label> </div> <div class="checkbox-option"> <label><input type="checkbox" id="hideFullRaces"> Hide Full Races</label> </div> </div> <div class="filter-buttons"> <button id="refreshRaces" class="gui-button">Refresh List</button> <button id="toggleFilters" class="gui-button active">Disable Filters</button> </div> </div> </div> `; raceList.parentNode.insertBefore(filterContainer, raceList); const filterElements = [ 'filterTrack', 'filterMinLaps', 'filterMaxLaps', 'filterMinDrivers', 'filterMaxDrivers', 'filterSort', 'hidePassworded', 'showSuitableCarsOnly', 'hideFullRaces' ]; filterElements.forEach(id => { const element = document.getElementById(id); if (element) { element.addEventListener('change', () => { saveFilterState(); if (document.getElementById('toggleFilters')?.classList.contains('active')) { window.RaceFiltering?.filterRacesList(); } }); } }); const toggleBtn = document.getElementById('toggleFilters'); const refreshBtn = document.getElementById('refreshRaces'); if (toggleBtn) { toggleBtn.addEventListener('click', function() { const isEnabled = this.classList.toggle('active'); this.textContent = isEnabled ? 'Disable Filters' : 'Enable Filters'; if (isEnabled) { window.RaceFiltering?.filterRacesList(); } else { clearFilters(); } saveFilterState(); }); } if (refreshBtn) { refreshBtn.addEventListener('click', () => { if (DEBUG) console.log('[DEBUG] Refresh button clicked'); refreshRacesList(); }); } const filtersEnabled = restoreFilterState(); if (filtersEnabled && toggleBtn?.classList.contains('active')) { window.RaceFiltering?.filterRacesList(); } } if (document.readyState !== 'loading') { init(); } else { document.addEventListener('DOMContentLoaded', init); } })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址