/* ================================================================================ FTOL SPIDER — node_spider.js (UPDATED) ================================================================================ Changes: - headless: false (real browser mode) - defaultViewport: null - Added user agent - Added small delay after page load ================================================================================ */ const puppeteer = require('puppeteer'); const mysql = require('mysql2/promise'); const path = require('path'); const fs = require('fs'); const USER_DATA_DIR = path.resolve(__dirname, 'user_data'); const LOG_FILE = path.resolve(__dirname, 'debug.log'); const SCREENSHOT = path.resolve(__dirname, 'error.png'); function log(msg) { const line = '[' + new Date().toISOString() + '] ' + msg; console.log(line); try { fs.appendFileSync(LOG_FILE, line + '\n'); } catch(e) {} } (async () => { log('--- FTOL SPIDER STARTING ---'); const db = await mysql.createConnection({ host: '127.0.0.1', database: 'leaderboard', user: 'root', password: 'RiteFocus3~', charset: 'utf8mb4' }); log('Database connected.'); const browser = await puppeteer.launch({ headless: false, defaultViewport: null, args: [ '--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage' ], userDataDir: USER_DATA_DIR }); log('Browser launched.'); try { await pollLoop(db, browser); } finally { await browser.close(); await db.end(); log('--- FTOL SPIDER STOPPED ---'); } })(); async function pollLoop(db, browser) { while (true) { const [settings] = await db.query( 'SELECT poll_rate, timeout FROM ftol_settings WHERE id = 1' ); const pollRate = (settings[0]?.poll_rate || 6) * 1000; const timeoutMs = (settings[0]?.timeout || 30) * 1000; const [rows] = await db.query( 'SELECT id, layout_id, car_class_id FROM spider_outgoing ORDER BY priority ASC LIMIT 1' ); if (!rows.length) { log('No nodes available — waiting 10s...'); await sleep(10000); continue; } const node = rows[0]; const nodeId = node.id; const trackId = node.layout_id; const carClassId = node.car_class_id; log(`Node: Track ${trackId} / Class ${carClassId} | timeout: ${timeoutMs/1000}s`); await processNode(db, browser, nodeId, trackId, carClassId, timeoutMs); await sleep(pollRate); } } async function processNode(db, browser, nodeId, trackId, carClassId, timeoutMs) { const page = await browser.newPage(); await page.setUserAgent( 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ' + '(KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36' ); page.on('console', msg => { if (msg.type() === 'error') { log('PAGE ERROR: ' + msg.text()); } }); const targetUrl = `https://game.raceroom.com/leaderboard/?track=${trackId}&car_class=${carClassId}`; const jsonData = await new Promise((resolve) => { const timer = setTimeout(() => { log(`Timeout — empty node: Track ${trackId} / Class ${carClassId}`); resolve(null); }, timeoutMs); page.on('response', async (response) => { const url = response.url(); if (url.includes('/leaderboard/')) { log(' URL: ' + response.status() + ' ' + url); } if (url.includes('/leaderboard/listing/') && response.status() === 200) { try { const data = await response.json(); const results = data?.context?.c?.results; log(` *** CAPTURED: ${results ? results.length : 0} results ***`); clearTimeout(timer); resolve(data); } catch (e) { log(' JSON parse error: ' + e.message); } } }); page.goto(targetUrl, { waitUntil: 'networkidle2', timeout: timeoutMs + 5000 }) .then(async () => { log(' Page loaded.'); await page.waitForTimeout(2000); }) .catch(async (err) => { log(' goto error: ' + err.message); try { await page.screenshot({ path: SCREENSHOT }); log(' Screenshot saved to error.png'); } catch(e) {} }); }); await page.close(); const payload = jsonData ? JSON.stringify(jsonData) : '{"empty":true}'; await db.query( `INSERT INTO spider_staging (track_id, car_class_id, json_payload, json_size, datetimeCreated) VALUES (?, ?, ?, ?, NOW())`, [trackId, carClassId, payload, payload.length] ); log(` Stored to spider_staging: Track ${trackId} / Class ${carClassId} | ${payload.length} bytes`); await db.query('DELETE FROM spider_outgoing WHERE id = ?', [nodeId]); log(` Node ${nodeId} removed from spider_outgoing`); } function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); }