| const elements = { |
| username: document.getElementById('username'), |
| password: document.getElementById('password'), |
| studio: document.getElementById('studio'), |
| minPrice: document.getElementById('min_price'), |
| startBtn: document.getElementById('startBtn'), |
| stopBtn: document.getElementById('stopBtn'), |
| saveBtn: document.getElementById('saveBtn'), |
| downloadBtn: document.getElementById('downloadBtn'), |
| clearLogsBtn: document.getElementById('clearLogsBtn'), |
| errorBanner: document.getElementById('errorBanner'), |
| successBanner: document.getElementById('successBanner'), |
| connectionStatus: document.getElementById('connectionStatus'), |
| currentState: document.getElementById('currentState'), |
| totalItems: document.getElementById('totalItems'), |
| currentPage: document.getElementById('currentPage'), |
| lastRunTime: document.getElementById('lastRunTime'), |
| progressBar: document.getElementById('progressBar'), |
| progressLabel: document.getElementById('progressLabel'), |
| csvLabel: document.getElementById('csvLabel'), |
| logOutput: document.getElementById('logOutput'), |
| }; |
|
|
| let logCursor = 0; |
| let pollTimer = null; |
| let running = false; |
| let screenshotTimer = null; |
|
|
| function setBanner(element, message) { |
| if (!message) { |
| element.classList.add('hidden'); |
| element.textContent = ''; |
| return; |
| } |
| element.textContent = message; |
| element.classList.remove('hidden'); |
| } |
|
|
| function showError(message) { |
| setBanner(elements.errorBanner, message); |
| setBanner(elements.successBanner, ''); |
| } |
|
|
| function showSuccess(message) { |
| setBanner(elements.successBanner, message); |
| setBanner(elements.errorBanner, ''); |
| } |
|
|
| function clearBanners() { |
| setBanner(elements.errorBanner, ''); |
| setBanner(elements.successBanner, ''); |
| } |
|
|
| function readFormData() { |
| return { |
| username: elements.username.value.trim(), |
| password: elements.password.value, |
| studio: elements.studio.value.trim(), |
| min_price: elements.minPrice.value.trim(), |
| }; |
| } |
|
|
| function setRunningState(isRunning) { |
| running = isRunning; |
| elements.startBtn.disabled = isRunning; |
| elements.stopBtn.disabled = !isRunning; |
| elements.connectionStatus.textContent = isRunning ? 'Running' : 'Ready'; |
| if (isRunning) { |
| elements.connectionStatus.parentElement.querySelector('.dot').style.background = '#f59e0b'; |
| } else { |
| elements.connectionStatus.parentElement.querySelector('.dot').style.background = '#22c55e'; |
| } |
| } |
|
|
| function updateStatusPanel(data) { |
| elements.currentState.textContent = data.current_state || 'Idle'; |
| elements.totalItems.textContent = String(data.total_items_found ?? 0); |
| elements.currentPage.textContent = String(data.current_page ?? 0); |
| elements.lastRunTime.textContent = data.last_run_time || '-'; |
| const progress = Number(data.progress || 0); |
| elements.progressBar.style.width = `${Math.max(0, Math.min(100, progress))}%`; |
| elements.progressLabel.textContent = `${Math.max(0, Math.min(100, progress))}%`; |
| elements.csvLabel.textContent = data.last_csv_path ? `Latest CSV: ${data.last_csv_path}` : 'No CSV generated yet'; |
| if (data.last_error) { |
| showError(data.last_error); |
| } |
| } |
|
|
| function appendLogs(logs) { |
| if (!Array.isArray(logs) || logs.length <= logCursor) { |
| return; |
| } |
| const newLogs = logs.slice(logCursor); |
| if (!newLogs.length) { |
| return; |
| } |
| const current = elements.logOutput.textContent === 'Waiting for the next run...' ? '' : elements.logOutput.textContent; |
| const merged = [current.trimEnd(), ...newLogs].filter(Boolean).join('\n'); |
| elements.logOutput.textContent = merged; |
| elements.logOutput.scrollTop = elements.logOutput.scrollHeight; |
| logCursor = logs.length; |
| } |
|
|
| async function fetchStatus() { |
| try { |
| const response = await fetch('/status', { cache: 'no-store' }); |
| const data = await response.json(); |
| setRunningState(Boolean(data.running)); |
| updateStatusPanel(data); |
| appendLogs(data.logs || []); |
| if (!data.running && pollTimer) { |
| window.clearInterval(pollTimer); |
| pollTimer = null; |
| } |
| } catch (error) { |
| console.error(error); |
| } |
| } |
|
|
| async function startBot() { |
| clearBanners(); |
| const payload = readFormData(); |
|
|
| if (!payload.username || !payload.password || !payload.studio) { |
| showError('Please enter username, password, and a studio name or URL.'); |
| return; |
| } |
|
|
| if (!payload.min_price || Number.isNaN(Number(payload.min_price))) { |
| showError('Minimum price must be a number.'); |
| return; |
| } |
|
|
| try { |
| const response = await fetch('/start', { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify(payload), |
| }); |
| const data = await response.json(); |
| if (!response.ok || !data.ok) { |
| showError(data.error || 'Failed to start automation.'); |
| return; |
| } |
|
|
| showSuccess('Automation started.'); |
| logCursor = 0; |
| elements.logOutput.textContent = 'Waiting for the next run...'; |
| setRunningState(true); |
| if (!pollTimer) { |
| pollTimer = window.setInterval(fetchStatus, 2000); |
| } |
| await fetchStatus(); |
| } catch (error) { |
| showError(error.message || 'Unable to start the bot.'); |
| } |
| } |
|
|
| async function stopBot() { |
| try { |
| const response = await fetch('/stop', { method: 'POST' }); |
| const data = await response.json(); |
| showSuccess(data.message || 'Stop requested.'); |
| await fetchStatus(); |
| } catch (error) { |
| showError(error.message || 'Unable to stop the bot.'); |
| } |
| } |
|
|
| async function saveSettings() { |
| clearBanners(); |
| const payload = readFormData(); |
|
|
| try { |
| const response = await fetch('/save-settings', { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify(payload), |
| }); |
| const data = await response.json(); |
| if (!response.ok || !data.ok) { |
| showError(data.error || 'Unable to save settings.'); |
| return; |
| } |
| showSuccess('Settings saved.'); |
| } catch (error) { |
| showError(error.message || 'Unable to save settings.'); |
| } |
| } |
|
|
| function clearLogs() { |
| logCursor = 0; |
| elements.logOutput.textContent = 'Waiting for the next run...'; |
| } |
|
|
| function boot() { |
| elements.startBtn.addEventListener('click', startBot); |
| elements.stopBtn.addEventListener('click', stopBot); |
| elements.saveBtn.addEventListener('click', saveSettings); |
| elements.clearLogsBtn.addEventListener('click', clearLogs); |
| elements.downloadBtn.addEventListener('click', () => { |
| elements.downloadBtn.setAttribute('href', '/download'); |
| }); |
|
|
| setRunningState(false); |
| fetchStatus(); |
| pollTimer = window.setInterval(fetchStatus, 2000); |
| |
| const img = document.getElementById('screenshotImg'); |
| if (img) { |
| const refresh = () => { |
| img.src = '/screenshot?ts=' + Date.now(); |
| }; |
| |
| screenshotTimer = window.setInterval(refresh, 3000); |
| |
| refresh(); |
| } |
| } |
|
|
| document.addEventListener('DOMContentLoaded', boot); |
|
|