import { pushPrice, updateBid, updateStatusByPrice, } from "../../system/apis/bid.js"; import axios from "../../system/axios.js"; import { delay, extractNumber, isTimeReached, removeFalsyValues, } from "../../system/utils.js"; import { ProductBid } from "../product-bid.js"; export class GraysProductBid extends ProductBid { constructor({ ...prev }) { super(prev); } getCloseTime = async () => { try { if (!this.page_context) return null; await this.page_context.waitForSelector("#lot-closing-datetime", { timeout: 3000, }); return await this.page_context.$eval( "#lot-closing-datetime", (el) => el.value ); } catch (error) { return null; } }; getPriceWasBid = async () => { try { if (!this.page_context) return null; await this.page_context.waitForSelector( "#biddableLot form div div:nth-child(1) span span", { timeout: 3000 } ); const element = await this.page_context.$( "#biddableLot form div div:nth-child(1) span span" ); const textPrice = await this.page_context.evaluate( (el) => el.textContent, element ); return extractNumber(textPrice) || null; } catch (error) { return null; } }; async isCloseProduct() { const close_time = await this.getCloseTime(); const currentUrl = await this.page_context.url(); if (currentUrl !== this.url) { return { result: false, close_time }; } if (!close_time) { const priceWasBid = await this.getPriceWasBid(); await updateStatusByPrice(this.id, priceWasBid); return { result: true, close_time: null }; } await delay(500); if (!close_time || new Date(close_time).getTime() <= new Date().getTime()) { console.log(`❌ [${this.id}] Product is close ${close_time}`); return { result: true, close_time }; } return { result: false, close_time }; } async placeBid() { try { await this.page_context.evaluate(() => { document.querySelector("#price").value = ""; }); console.log("✅ Cleared price"); await this.page_context.type("#price", String(this.max_price)); console.log("✅ Typed max price"); await delay(5000); const currentValue = await this.page_context.$eval( "#price", (el) => el.value ); console.log("✅ Checked currentValue:", currentValue); if (currentValue !== String(this.max_price)) { console.warn( `[${this.id}] Value not match #price: ${currentValue} !== ${this.max_price}` ); return; } await this.page_context.click("#btnSubmit"); console.log("✅ Clicked submit"); await delay(1000); await this.page_context.waitForSelector("button", { timeout: 5000 }); await delay(5000); // await this.page_context.click("button"); // await this.page_context.waitForNavigation({ timeout: 5000 }); // await this.page_context.waitForFunction( // () => document.body.innerText.includes("Successfully"), // { timeout: 5000 } // hoặc lâu hơn nếu cần // ); // console.log("✅ Found 'Successfully'"); // await pushPrice({ // bid_id: this.id, // price: this.max_price, // }); // await this.handleReturnProductPage(); if (this.isSandbox()) { await this.handlePlaceBidSandbox(); } else { await this.handlePlaceBidLive(); } return true; } catch (error) { await this.page_context.goto(this.url); console.log(error); console.log(`❌ [${this.id}] Error in placeBid: ${error.message}`); return false; } finally { global.IS_CLEANING = true; } } async handlePlaceBidLive() { try { global.IS_CLEANING = false; await this.page_context.click("button"); await this.page_context.waitForNavigation({ timeout: 5000 }); // await this.page_context.waitForFunction( // () => document.body.innerText.includes("Successfully"), // { timeout: 5000 } // hoặc lâu hơn nếu cần // ); console.log("✅ Found 'Successfully'"); await pushPrice({ bid_id: this.id, price: this.max_price, }); await this.handleReturnProductPage(); } finally { global.IS_CLEANING = true; } } async handlePlaceBidSandbox() { // call to trickger server send mail await axios({ url: this.ACTION_URL(), method: "POST", data: { id: this.id, record_url: `${process.env.BASE_URL}admin/bids/record/${this.name_record}`, }, }); await this.close(); } async handleReturnProductPage() { await this.page_context.goto(this.url); await delay(1000); } async handleUpdateBid({ lot_id, close_time, name, current_price, reserve_price, }) { const response = await updateBid(this.id, { lot_id, close_time, name, current_price, reserve_price: Number(reserve_price) || 0, }); if (response) { this.lot_id = response.lot_id; this.close_time = response.close_time; this.start_bid_time = response.start_bid_time; } } update = async () => { if (!this.page_context) return; const page = this.page_context; try { const close_time = await this.getCloseTime(); // Chờ phần tử xuất hiện trước khi lấy giá trị await page .waitForSelector("#priceValue", { timeout: 5000 }) .catch(() => null); const price_value = await page .$eval("#priceValue", (el) => el.value) .catch(() => null); await page.waitForSelector("#lotId", { timeout: 5000 }).catch(() => null); const lot_id = await page .$eval("#lotId", (el) => el.value) .catch(() => null); await page .waitForSelector("#placebid-sticky > div:nth-child(2) > div > h3", { timeout: 5000, }) .catch(() => null); const name = await page .$eval(".dls-heading-3.lotPageTitle", (el) => el.innerText) .catch(() => null); await page .waitForSelector( "#biddableLot > form > div > div:nth-child(1) > div:nth-child(1) > div:nth-child(2) > div > span > span", { timeout: 5000 } ) .catch(() => null); const current_price = await page .$eval( "#biddableLot > form > div > div:nth-child(1) > div:nth-child(1) > div:nth-child(2) > div > span > span", (el) => el.innerText ) .catch(() => null); console.log( `📌 [${this.id}] Product Info: Lot ID: ${lot_id}, Name: ${name}, Current Price: ${current_price}, Reserve price: ${price_value}` ); const data = removeFalsyValues( { lot_id, reserve_price: price_value, close_time: close_time ? String(close_time) : null, name, current_price: current_price ? extractNumber(current_price) : null, }, ["close_time"] ); this.handleUpdateBid(data); return { price_value, lot_id, name, current_price }; } catch (error) { console.error(`🚨 Error updating product info: ${error.message}`); return null; } }; getCurrentData = async () => { if (!this.page_context) return null; try { // Lấy thời gian đóng const close_time = await this.getCloseTime(); // Giá trị reserve price await this.page_context .waitForSelector("#priceValue", { timeout: 5000 }) .catch(() => null); const price_value = await this.page_context .$eval("#priceValue", (el) => el.value) .catch(() => null); // Lot ID await this.page_context .waitForSelector("#lotId", { timeout: 5000 }) .catch(() => null); const lot_id = await this.page_context .$eval("#lotId", (el) => el.value) .catch(() => null); // Tên sản phẩm await this.page_context .waitForSelector(".dls-heading-3.lotPageTitle", { timeout: 5000 }) .catch(() => null); const name = await this.page_context .$eval(".dls-heading-3.lotPageTitle", (el) => el.innerText) .catch(() => null); // Giá hiện tại await this.page_context .waitForSelector( "#biddableLot > form > div > div:nth-child(1) > div:nth-child(1) > div:nth-child(2) > div > span > span", { timeout: 5000 } ) .catch(() => null); const current_price_raw = await this.page_context .$eval( "#biddableLot > form > div > div:nth-child(1) > div:nth-child(1) > div:nth-child(2) > div > span > span", (el) => el.innerText ) .catch(() => null); const current_price = current_price_raw ? extractNumber(current_price_raw) : null; return removeFalsyValues( { lot_id, reserve_price: Number(price_value) || 0, close_time: close_time ? String(close_time) : null, name, current_price, }, ["close_time"] ); } catch (error) { console.error(`🚨 Error fetching current product data: ${error.message}`); return null; } }; async handlePlaceBid() { if (!this.page_context) { console.log( `⚠️ [${this.id}] No page context found, aborting bid process.` ); return; } if (global[`IS_PLACE_BID-${this.id}`]) { console.log(`⚠️ [${this.id}] Bid is already in progress, skipping.`); return; } try { console.log(`🔄 [${this.id}] Starting bid process...`); global[`IS_PLACE_BID-${this.id}`] = true; // Tắt clearLazyTab vì web này phải navigate để bid global.IS_CLEANING = false; const isCloseProduct = await this.isCloseProduct(); if (isCloseProduct.result) { console.log( `⚠️ [${this.id}] Outbid detected, calling outBid function.` ); await outBid(this.id); return; } const biddedData = this.getBidedData(); const isBided = (biddedData || []).find( (item) => item.model === this.model && item.max_price === this.max_price ); if (isBided) { if (this.histories.length <= 0 && isTimeReached(this.start_bid_time)) { pushPrice({ bid_id: this.id, price: this.max_price, }); } console.log(`[${this.id}] This item bided. Skipping...`); global[`IS_PLACE_BID-${this.id}`] = false; global.IS_CLEANING = true; return; } if (!this.start_bid_time || !this.close_time) { console.log( `[${this.id}] Skipping processing: auction has started but not yet closed.` ); global.IS_CLEANING = true; return; } // Kiểm tra nếu giá hiện tại lớn hơn giá tối đa cộng thêm giá cộng thêm if (this.current_price > this.max_price + this.plus_price) { console.log(`⚠️ [${this.id}] Outbid bid`); // Ghi log cảnh báo nếu giá hiện tại vượt quá mức tối đa cho phép global.IS_CLEANING = true; return; // Dừng hàm nếu giá đã vượt qua giới hạn } // Kiểm tra thời gian bid if (this.start_bid_time && !isTimeReached(this.start_bid_time)) { console.log( `⏳ [${this.id}] Not yet time to bid. Skipping Product: ${ this.name || "None" }` ); global.IS_CLEANING = true; return; } if (this.histories.length > 0) { console.log( `[${this.id}] Already biding with price ${this.histories[0].price}` ); return; } console.log( "-------------------------------------BIDING---------------------------------------" ); await this.startRecordSandbox(); const result = await this.placeBid(); global.IS_CLEANING = true; global[`IS_PLACE_BID-${this.id}`] = false; } catch (error) { console.log(`🚨 [${this.id}] Error placing bid: ${error.message}`); } finally { await this.stopRecordSandbox(); console.log(`🔚 [${this.id}] Resetting bid flag.`); } } getBidedData() { return global[`BIDED_DATA_${this.web_bid?.origin_url}`]; } action = async () => { try { const page = this.page_context; // 📌 Kiểm tra nếu trang chưa tải đúng URL thì điều hướng đến URL mục tiêu if (!page.url() || !page.url().includes(this.url)) { console.log(`🔄 [${this.id}] Navigating to target URL: ${this.url}`); await this.gotoLink(); } await this.handlePlaceBid(); } catch (error) { console.error( `🚨 [${this.id}] Error navigating the page: ${error.message}` ); } }; }