325 lines
		
	
	
		
			9.7 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
			
		
		
	
	
			325 lines
		
	
	
		
			9.7 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
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}`);
 | 
						|
    }
 | 
						|
  };
 | 
						|
}
 |