const socket = io(); let sessionId = null; let isConnected = false; let isProgressLineInputFocused = false; // New flag to track focus const PASTE_LINE_LIMIT_COMBO = 10000; const PASTE_LINE_LIMIT_PROXY = 5000; // --- stop locks --- let isStopping = false; let forceStopped = false; function setControlsDisabled(disabled) { ['start-button','pause-button','continue-button','set-progress-button'] .forEach(id => { const el = document.getElementById(id); if (el) el.disabled = disabled; }); } function resetUIToFreshSession() { const zeros = {'total-lines':'0','checked':'0','invalid':'0','hits':'0','custom':'0','total-mega-fan':'0','total-fan-member':'0','total-ultimate-mega':'0','errors':'0','retries':'0','cpm':'0','elapsed-time':'0:00:00'}; Object.entries(zeros).forEach(([id,val]) => { const el = document.getElementById(id); if (el) el.textContent = val; }); const statusEl = document.getElementById('status'); if (statusEl) { statusEl.textContent = '❌ STOPPED'; statusEl.style.color = '#ff6b6b'; } const combo = document.getElementById('combo-input'); if (combo) { combo.value=''; combo.style.height='auto'; } const proxy = document.getElementById('proxy-input'); if (proxy) { proxy.value=''; proxy.style.height='auto'; } const progress = document.getElementById('progress-line-input'); if (progress) progress.value = 0; } function showStatusMessage(message, type = 'info') { const statusContainer = document.getElementById('status-messages'); const messageDiv = document.createElement('div'); messageDiv.className = `status-message ${type}`; messageDiv.textContent = message; statusContainer.appendChild(messageDiv); setTimeout(() => { if (messageDiv.parentNode) { messageDiv.style.opacity = '0'; messageDiv.style.transform = 'translateX(100%)'; setTimeout(() => { if (messageDiv.parentNode) { statusContainer.removeChild(messageDiv); } }, 300); } }, 5000); } socket.on('connect', () => { isConnected = true; showStatusMessage('Connected to server successfully!', 'success'); const storedSessionId = localStorage.getItem('sessionId'); if (storedSessionId) { socket.emit('reconnect_session', { session_id: storedSessionId }); } else { socket.emit('request_session'); } }); socket.on('disconnect', () => { isConnected = false; showStatusMessage('Disconnected from server!', 'error'); }); socket.on('session_created', (data) => { sessionId = data.session_id; localStorage.setItem('sessionId', sessionId); console.log(`Session created: ${sessionId}`); showStatusMessage('Session created successfully!', 'success'); history.pushState({ sessionId: sessionId }, '', `?session=${sessionId}`); document.getElementById('combo-input').value = ''; document.getElementById('proxy-input').value = ''; document.getElementById('progress-line-input').value = 0; document.getElementById('threads-input').value = 10; document.getElementById('proxy-type-select').value = 'http'; updateStatsDisplay({ status: '❌ STOPPED', total_lines: 0, checked: 0, invalid: 0, hits: 0, custom: 0, total_mega_fan: 0, total_fan_member: 0, total_ultimate_mega: 0, errors: 0, retries: 0, cpm: 0, elapsed_time: '0:00:00' }); }); socket.on('session_reconnected', (data) => { sessionId = data.session_id; console.log(`Session reconnected: ${sessionId}`); showStatusMessage('Session reconnected successfully!', 'success'); history.pushState({ sessionId: sessionId }, '', `?session=${sessionId}`); if (data.previous_state) { updateStatsDisplay(data.previous_state.stats); document.getElementById('combo-input').value = data.previous_state.combo_file_uploaded ? `Combo file uploaded (${data.previous_state.stats.total_lines} lines). Ready to check. ✅` : ''; document.getElementById('proxy-input').value = data.previous_state.proxy_file_uploaded ? `Proxy file uploaded. Ready to check. ✅` : ''; document.getElementById('threads-input').value = data.previous_state.threads || 10; document.getElementById('proxy-type-select').value = data.previous_state.proxy_type || 'http'; // Only update progress-line-input if it's not currently focused if (!isProgressLineInputFocused) { document.getElementById('progress-line-input').value = data.previous_state.stats.checked || 0; } const textareas = document.querySelectorAll('.upload-textarea'); textareas.forEach(textarea => { textarea.style.height = 'auto'; textarea.style.height = Math.min(textarea.scrollHeight, 300) + 'px'; }); } }); socket.on('stats_update', (data) => { if ((isStopping || forceStopped) && !(data.status && data.status.includes('STOPPED'))) { return; } updateStatsDisplay(data); const progressLineInput = document.getElementById('progress-line-input'); // Only update progress-line-input if it's not currently focused AND its value matches the current checked count // This prevents overwriting user input if they are actively changing it. if (!isProgressLineInputFocused && parseInt(progressLineInput.value) === data.checked) { progressLineInput.value = data.checked; } }); socket.on('error', (data) => { showStatusMessage(data.message, 'error'); }); socket.on('combo_uploaded', (data) => { showStatusMessage(data.message, 'success'); document.getElementById('total-lines').textContent = data.count; document.getElementById('combo-input').value = `Combo file uploaded (${data.count} lines). Ready to check. ✅`; const textarea = document.getElementById('combo-input'); textarea.style.height = 'auto'; textarea.style.height = Math.min(textarea.scrollHeight, 300) + 'px'; }); socket.on('proxy_uploaded', (data) => { showStatusMessage(data.message, 'success'); document.getElementById('proxy-input').value = `Proxy file uploaded (${data.count} lines). Ready to check. ✅`; document.getElementById('proxy-type-select').value = data.proxy_type; const textarea = document.getElementById('proxy-input'); textarea.style.height = 'auto'; textarea.style.height = Math.min(textarea.scrollHeight, 300) + 'px'; }); socket.on('checker_started', (data) => { showStatusMessage(data.message, 'success'); }); socket.on('checker_stopped', (data) => { forceStopped = true; isStopping = false; resetUIToFreshSession(); setControlsDisabled(false); showStatusMessage(data.message, 'info'); }); socket.on('checker_paused', (data) => { showStatusMessage(data.message, 'info'); }); socket.on('checker_continued', (data) => { showStatusMessage(data.message, 'success'); }); socket.on('checker_completed', (data) => { showStatusMessage(data.message, 'success'); }); socket.on('inputs_cleared', () => { forceStopped = true; isStopping = false; resetUIToFreshSession(); setControlsDisabled(false); }); socket.on('hits_available', (data) => { showStatusMessage('Hits file is ready for download!', 'success'); downloadFile(data.content, data.filename); }); socket.on('hits_download', (data) => { downloadFile(data.content, data.filename); showStatusMessage('Hits downloaded successfully!', 'success'); }); socket.on('progress_line_updated', (data) => { showStatusMessage(data.message, 'success'); // Update the displayed checked count and the input field document.getElementById('checked').textContent = data.new_checked_count; document.getElementById('progress-line-input').value = data.new_checked_count; }); function updateStatsDisplay(data) { document.getElementById('status').textContent = data.status; document.getElementById('total-lines').textContent = data.total_lines; document.getElementById('checked').textContent = data.checked; document.getElementById('invalid').textContent = data.invalid; document.getElementById('hits').textContent = data.hits; document.getElementById('custom').textContent = data.custom; document.getElementById('total-mega-fan').textContent = data.total_mega_fan; document.getElementById('total-fan-member').textContent = data.total_fan_member; document.getElementById('total-ultimate-mega').textContent = data.total_ultimate_mega; document.getElementById('errors').textContent = data.errors; document.getElementById('retries').textContent = data.retries; document.getElementById('cpm').textContent = data.cpm; document.getElementById('elapsed-time').textContent = data.elapsed_time; const statusElement = document.getElementById('status'); if (data.status.includes('RUNNING')) { statusElement.style.color = '#4ecdc4'; } else if (data.status.includes('PAUSED')) { statusElement.style.color = '#feca57'; } else if (data.status.includes('COMPLETE')) { statusElement.style.color = '#4ecdc4'; } else { statusElement.style.color = '#ff6b6b'; } } function downloadFile(content, filename) { const blob = new Blob([content], { type: 'text/plain' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = filename; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); } function validateSession() { if (!sessionId) { showStatusMessage('No active session. Please refresh the page or wait for connection.', 'error'); return false; } if (!isConnected) { showStatusMessage('Not connected to server. Please check your connection.', 'error'); return false; } return true; } function uploadFileViaHttp(file, fileType) { if (!validateSession()) return; const formData = new FormData(); formData.append('file', file); formData.append('session_id', sessionId); formData.append('file_type', fileType); if (fileType === 'proxy') { formData.append('proxy_type', document.getElementById('proxy-type-select').value); } const xhr = new XMLHttpRequest(); xhr.open('POST', '/upload_file', true); xhr.onload = function() { if (xhr.status === 200) { const response = JSON.parse(xhr.responseText); console.log(`${fileType} upload success:`, response.message); // Emit the success event to update UI // These events are already handled by socket.io.on('combo_uploaded') etc. // No need to manually emit here. The server will emit. } else { const errorResponse = JSON.parse(xhr.responseText); showStatusMessage(`Error uploading ${fileType}: ${errorResponse.message}`, 'error'); console.error(`Error uploading ${fileType}:`, errorResponse); if (fileType === 'combo') document.getElementById('combo-input').value = ''; if (fileType === 'proxy') document.getElementById('proxy-input').value = ''; } }; xhr.onerror = function() { showStatusMessage(`Network error during ${fileType} upload. Please check server connection.`, 'error'); console.error(`Network error during ${fileType} upload.`); if (fileType === 'combo') document.getElementById('combo-input').value = ''; if (fileType === 'proxy') document.getElementById('proxy-input').value = ''; }; xhr.send(formData); showStatusMessage(`Uploading ${fileType} file...`, 'info'); } function uploadContentViaWebSocket(content, fileType) { if (!validateSession()) return; if (fileType === 'combo') { socket.emit('upload_combo', { session_id: sessionId, content: content }); } else if (fileType === 'proxy') { socket.emit('upload_proxy', { session_id: sessionId, content: content, proxy_type: document.getElementById('proxy-type-select').value }); } showStatusMessage(`Processing pasted ${fileType} content...`, 'info'); } document.addEventListener('DOMContentLoaded', function() { function triggerFileInput(fileType) { const input = document.createElement('input'); input.type = 'file'; input.accept = '.txt'; input.onchange = (e) => { const file = e.target.files[0]; if (file) { uploadFileViaHttp(file, fileType); } }; input.click(); } document.getElementById('upload-combo-button').addEventListener('click', () => { if (!validateSession()) return; const comboContent = document.getElementById('combo-input').value.trim(); if (comboContent) { const lineCount = comboContent.split('\n').length; if (lineCount > PASTE_LINE_LIMIT_COMBO) { showStatusMessage(`Pasted combo content is too large (${lineCount} lines). Please use the file upload dialog for files larger than ${PASTE_LINE_LIMIT_COMBO} lines.`, 'error'); document.getElementById('combo-input').value = ''; return; } uploadContentViaWebSocket(comboContent, 'combo'); } else { triggerFileInput('combo'); } }); document.getElementById('upload-proxy-button').addEventListener('click', () => { if (!validateSession()) return; const proxyContent = document.getElementById('proxy-input').value.trim(); if (proxyContent) { const lineCount = proxyContent.split('\n').length; if (lineCount > PASTE_LINE_LIMIT_PROXY) { showStatusMessage(`Pasted proxy content is too large (${lineCount} lines). Please use the file upload dialog for files larger than ${PASTE_LINE_LIMIT_PROXY} lines.`, 'error'); document.getElementById('proxy-input').value = ''; return; } uploadContentViaWebSocket(proxyContent, 'proxy'); } else { triggerFileInput('proxy'); } }); document.getElementById('start-button').addEventListener('click', () => { forceStopped = false; if (!validateSession()) return; const threads = parseInt(document.getElementById('threads-input').value); const initialProgressLine = parseInt(document.getElementById('progress-line-input').value); const proxyType = document.getElementById('proxy-type-select').value; // Get current proxy type if (isNaN(threads) || threads < 1 || threads > 400) { showStatusMessage('Please enter a valid thread count (1-400)!', 'error'); return; } if (isNaN(initialProgressLine) || initialProgressLine < 0) { showStatusMessage('Please enter a valid non-negative number for the progress line!', 'error'); return; } socket.emit('start_checker', { session_id: sessionId, threads: threads, initial_progress_line: initialProgressLine, proxy_type: proxyType // Pass proxy type to backend }); }); document.getElementById('stop-button').addEventListener('click', () => { if (!validateSession()) return; if (isStopping) return; isStopping = true; forceStopped = true; setControlsDisabled(true); showStatusMessage('Stopping... please wait a moment.', 'info'); socket.emit('stop_checker', { session_id: sessionId }); }); document.getElementById('pause-button').addEventListener('click', () => { if (!validateSession()) return; socket.emit('pause_checker', { session_id: sessionId }); }); document.getElementById('continue-button').addEventListener('click', () => { forceStopped = false; if (!validateSession()) return; const currentThreads = parseInt(document.getElementById('threads-input').value); const currentProxyType = document.getElementById('proxy-type-select').value; socket.emit('continue_checker', { session_id: sessionId, threads: currentThreads, proxy_type: currentProxyType }); }); document.getElementById('download-hits-button').addEventListener('click', () => { if (!validateSession()) return; socket.emit('download_hits', { session_id: sessionId }); }); const progressLineInput = document.getElementById('progress-line-input'); progressLineInput.addEventListener('focus', () => { isProgressLineInputFocused = true; }); progressLineInput.addEventListener('blur', () => { isProgressLineInputFocused = false; }); document.getElementById('set-progress-button').addEventListener('click', () => { if (!validateSession()) return; const newProgressLine = parseInt(document.getElementById('progress-line-input').value); if (isNaN(newProgressLine) || newProgressLine < 0) { showStatusMessage('Please enter a valid non-negative number for the progress line!', 'error'); return; } socket.emit('set_progress_line', { session_id: sessionId, progress_line: newProgressLine }); }); const textareas = document.querySelectorAll('.upload-textarea'); textareas.forEach(textarea => { textarea.addEventListener('input', function() { this.style.height = 'auto'; this.style.height = Math.min(this.scrollHeight, 300) + 'px'; }); textarea.addEventListener('paste', function(event) { const pastedText = (event.clipboardData || window.clipboardData).getData('text'); const lineCount = pastedText.split('\n').length; const isComboTextarea = this.id === 'combo-input'; const limit = isComboTextarea ? PASTE_LINE_LIMIT_COMBO : PASTE_LINE_LIMIT_PROXY; if (lineCount > limit) { event.preventDefault(); showStatusMessage(`Pasted ${isComboTextarea ? 'combo' : 'proxy'} content is too large (${lineCount} lines). Please use the file upload dialog for files larger than ${limit} lines.`, 'error'); this.value = ''; } }); }); document.addEventListener('keydown', function(e) { if (e.ctrlKey || e.metaKey) { switch(e.key) { case 's': e.preventDefault(); document.getElementById('start-button').click(); break; case 'q': e.preventDefault(); document.getElementById('stop-button').click(); break; case 'p': e.preventDefault(); document.getElementById('pause-button').click(); break; case 'r': e.preventDefault(); document.getElementById('continue-button').click(); break; case 'd': e.preventDefault(); document.getElementById('download-hits-button').click(); break; case 'g': e.preventDefault(); document.getElementById('set-progress-button').click(); break; } } }); document.getElementById('start-button').title = 'Start Checker (Ctrl+S)'; document.getElementById('stop-button').title = 'Stop Checker (Ctrl+Q)'; document.getElementById('pause-button').title = 'Pause Checker (Ctrl+P)'; document.getElementById('continue-button').title = 'Continue Checker (Ctrl+R)'; document.getElementById('download-hits-button').title = 'Download Hits (Ctrl+D)'; document.getElementById('set-progress-button').title = 'Set Progress Line (Ctrl+G)'; const urlParams = new URLSearchParams(window.location.search); const urlSessionId = urlParams.get('session'); if (urlSessionId) { localStorage.setItem('sessionId', urlSessionId); } }); document.addEventListener('click', function(e) { if (e.target.classList.contains('btn-3d')) { const ripple = document.createElement('span'); const rect = e.target.getBoundingClientRect(); const size = Math.max(rect.width, rect.height); const x = e.clientX - rect.left - size / 2; const y = e.clientY - rect.top - size / 2; ripple.style.width = ripple.style.height = size + 'px'; ripple.style.left = x + 'px'; ripple.style.top = y + 'px'; ripple.classList.add('ripple'); e.target.appendChild(ripple); setTimeout(() => { if (ripple.parentNode) { ripple.parentNode.removeChild(ripple); } }, 600); } }); const style = document.createElement('style'); style.textContent = ` .btn-3d { position: relative; overflow: hidden; } .ripple { position: absolute; border-radius: 50%; background: rgba(255, 255, 255, 0.6); transform: scale(0); animation: ripple-animation 0.6s linear; pointer-events: none; } @keyframes ripple-animation { to { transform: scale(4); opacity: 0; } } `; document.head.appendChild(style);