import path from "path"; import { createOutBidLog } from "../../system/apis/out-bid-log.js"; import configs from "../../system/config.js"; import { delay, extractNumber, getPathProfile, isTimeReached, safeClosePage, } from "../../system/utils.js"; import { ApiBid } from "../api-bid.js"; import fs from "fs"; export class GrayApiBid extends ApiBid { retry_login = 0; retry_login_count = 3; constructor({ ...prev }) { super(prev); } async polling(page) { try { // // 🔥 Xóa tất cả event chặn request trước khi thêm mới // page.removeAllListeners('request'); // await page.setRequestInterception(true); // page.on('request', (request) => { // if (request.url().includes('api/Notifications/GetOutBidLots')) { // console.log('🚀 Fake response cho request:', request.url()); // const fakeData = fs.readFileSync('./data/fake-out-lots.json', 'utf8'); // request.respond({ // status: 200, // contentType: 'application/json', // body: fakeData, // }); // } else { // try { // request.continue(); // ⚠️ Chỉ tiếp tục nếu request chưa bị chặn // } catch (error) { // console.error('⚠️ Lỗi khi tiếp tục request:', error.message); // } // } // }); console.log(`🔄 [${this.id}] Starting polling process...`); await page.evaluateHandle( (apiUrl, interval, bidId) => { if (window._autoBidPollingStarted) { console.log( `✅ [${bidId}] Polling is already running. Skipping initialization.` ); return; } console.log(`🚀 [${bidId}] Initializing polling...`); window._autoBidPollingStarted = true; function sendRequest() { console.log( `📡 [${bidId}] Sending request to track out-bid lots...` ); fetch(apiUrl, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: JSON.stringify({ timeStamp: new Date().getTime() }), }) .then((response) => console.log( `✅ [${bidId}] Response received: ${response.status}` ) ) .catch((err) => console.error(`⚠️ [${bidId}] Request error:`, err) ); } window._pollingInterval = setInterval(sendRequest, interval); }, configs.WEB_CONFIGS.GRAYS.API_CALL_TO_TRACKING, configs.WEB_CONFIGS.GRAYS.AUTO_CALL_API_TO_TRACKING, this.id ); // extractLotsAndBids await this.extractLotsAndBids(); } catch (error) { if (error.message.includes("Execution context was destroyed")) { console.log( `⚠️ [${this.id}] Page reload detected, restarting polling...` ); await page .waitForNavigation({ waitUntil: "networkidle2" }) .catch(() => {}); return await this.polling(page); } console.error(`🚨 [${this.id}] Unexpected polling error:`, error); throw error; } } async handleCreateLogsOnServer(data) { if (!Array.isArray(data)) return; const values = data.map((item) => { return { model: item.Sku, lot_id: item.Id, out_price: extractNumber(item.Bid) || 0, raw_data: JSON.stringify(item), }; }); await createOutBidLog(values); } listen_out_bids = async (data) => { if (this.children.length <= 0 || data.length <= 0) return; // SAVE LOGS ON SERVER this.handleCreateLogsOnServer(data); const bidOutLots = data.filter( (bid) => !this.children_processing.some((item) => item.model === bid.Sku) ); // const handleChildren = this.children.filter((item) => // bidOutLots.some((i) => i.Sku === item.model) // ); // console.log({ // handleChildren, // children_processing: this.children_processing, // data, // bidOutLots, // }); // for (const product_tab of handleChildren) { // if (!isTimeReached(product_tab.start_bid_time)) { // console.log( // `❌ [${this.id}] It's not time yet ID: ${product_tab.id} continue waiting...` // ); // return; // } // this.children_processing.push(product_tab); // if (!product_tab.page_context) { // await product_tab.puppeteer_connect(); // } // await product_tab.action(); // this.children_processing = this.children_processing.filter( // (item) => item.id !== product_tab.id // ); // } }; isLogin = async () => { if (!this.page_context) return false; const filePath = getPathProfile(this.origin_url); if ( !(await this.page_context.$('input[name="username"]')) || fs.existsSync(filePath) ) { return true; } return false; }; async extractLotsAndBids(url) { if (!this.page_context) return; const data = await this.page_context.evaluate(() => { const rows = document.querySelectorAll("tr.current.open"); return Array.from(rows).map((row) => { const lotText = row.querySelector(".lot-number")?.textContent.trim() || ""; const autobidMatch = row.textContent.match( /Your maximum autobid is AU\s*\$([\d,]+)/ ); const lotMatch = lotText.match(/Lot No:\s*(\S+)/); return { model: lotMatch ? lotMatch[1] : null, max_price: autobidMatch ? parseFloat(autobidMatch[1].replace(/,/g, "")) : null, }; }); }); global[`BIDED_DATA_${this.origin_url}`] = data || []; } async handleLogin() { const page = this.page_context; global.IS_CLEANING = false; const filePath = getPathProfile(this.origin_url); // 🔍 Check if already logged in (login input should not be visible) if ( !(await page.$('input[name="username"]')) // || fs.existsSync(filePath) ) { console.log(`✅ [${this.id}] Already logged in, skipping login.`); global.IS_CLEANING = true; this.retry_login = 0; // Reset retry count return; } console.log(`🔑 [${this.id}] Starting login process...`); try { await page.type('input[name="username"]', this.username, { delay: 100 }); await page.type('input[name="password"]', this.password, { delay: 150 }); await page.click("#loginButton"); await Promise.race([ page.waitForNavigation({ timeout: 8000, waitUntil: "domcontentloaded", }), page.waitForFunction( () => !document.querySelector('input[name="username"]'), { timeout: 8000 } ), // Check if login input disappears ]); if (!(await page.$('input[name="username"]'))) { console.log(`✅ [${this.id}] Login successful!`); this.retry_login = 0; // Reset retry count after success return; } throw new Error("Login failed, login input is still visible."); } catch (error) { console.log( `⚠️ [${this.id}] Login error: ${error.message}. Retrying attempt ${ this.retry_login + 1 } ❌` ); this.retry_login++; if (this.retry_login > this.retry_login_count) { console.log( `🚨 [${this.id}] Maximum login attempts reached. Stopping login process.` ); safeClosePage(this); this.retry_login = 0; // Reset retry count return; } safeClosePage(this); // Close the current page await delay(1000); if (!this.page_context) { await this.puppeteer_connect(); // Reconnect if page is closed } return await this.action(); // Retry login } finally { global.IS_CLEANING = true; } } action = async () => { try { const page = this.page_context; await page.goto(this.url, { waitUntil: "networkidle2", timeout: 10000 }); console.log(`🌍 [${this.id}] Navigated to URL: ${this.url}`); await page.bringToFront(); console.log(`🎯 [${this.id}] Brought page to front.`); // Set userAgent await page.setUserAgent( "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" ); console.log(`🛠️ [${this.id}] UserAgent set.`); page.on("response", async (response) => { if ( response.request().url().includes("api/Notifications/GetOutBidLots") ) { console.log(`🚀 [${this.id}] API POST detected: ${response.url()}`); try { const responseBody = await response.json(); await this.listen_out_bids(responseBody.AuctionOutBidLots || []); } catch (error) { console.error( `❌ [${this.id}] Error processing response:`, error?.message ); } } }); page.on("load", async () => { console.log(`🔄 [${this.id}] Page has reloaded, restarting polling...`); await this.polling(page); await this.handleLogin(); }); await this.polling(page); // Call when first load await this.handleLogin(); } catch (error) { console.log(`❌ [${this.id}] Action error: ${error.message}`); } }; }