429 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
			
		
		
	
	
			429 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
import {
 | 
						|
  pushPrice,
 | 
						|
  updateBid,
 | 
						|
  updateStatusByPrice,
 | 
						|
} from "../../system/apis/bid.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 = "";
 | 
						|
      });
 | 
						|
 | 
						|
      await this.page_context.type("#price", String(this.max_price));
 | 
						|
 | 
						|
      await delay(5000);
 | 
						|
 | 
						|
      const currentValue = await this.page_context.$eval(
 | 
						|
        "#price",
 | 
						|
        (el) => el.value
 | 
						|
      );
 | 
						|
 | 
						|
      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");
 | 
						|
      await delay(1000);
 | 
						|
 | 
						|
      await this.page_context.waitForSelector("button", { timeout: 5000 });
 | 
						|
 | 
						|
      await delay(500);
 | 
						|
 | 
						|
      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();
 | 
						|
 | 
						|
      return true;
 | 
						|
    } catch (error) {
 | 
						|
      this.page_context.goto(this.url);
 | 
						|
      console.log(`❌ [${this.id}] Error in placeBid: ${error.message}`);
 | 
						|
      return false;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  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();
 | 
						|
 | 
						|
      console.log({
 | 
						|
        biddedData,
 | 
						|
      });
 | 
						|
 | 
						|
      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---------------------------------------"
 | 
						|
      );
 | 
						|
      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 {
 | 
						|
      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}`
 | 
						|
      );
 | 
						|
    }
 | 
						|
  };
 | 
						|
}
 |