344 lines
		
	
	
		
			9.1 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
			
		
		
	
	
			344 lines
		
	
	
		
			9.1 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
import * as fs from "fs";
 | 
						|
import path from "path";
 | 
						|
import BID_TYPE from "../system/bid-type.js";
 | 
						|
import browser from "../system/browser.js";
 | 
						|
import CONSTANTS from "../system/constants.js";
 | 
						|
import {
 | 
						|
  findEarlyLoginTime,
 | 
						|
  findNearestClosingChild,
 | 
						|
  getPathLocalData,
 | 
						|
  getPathProfile,
 | 
						|
  isTimeReached,
 | 
						|
  sanitizeFileName,
 | 
						|
  subtractSeconds,
 | 
						|
} from "../system/utils.js";
 | 
						|
import { Bid } from "./bid.js";
 | 
						|
 | 
						|
export class ApiBid extends Bid {
 | 
						|
  id;
 | 
						|
  account;
 | 
						|
  children = [];
 | 
						|
  children_processing = [];
 | 
						|
  created_at;
 | 
						|
  updated_at;
 | 
						|
  origin_url;
 | 
						|
  active;
 | 
						|
  // browser_context;
 | 
						|
  username;
 | 
						|
  password;
 | 
						|
  early_tracking_seconds;
 | 
						|
  snapshot_at;
 | 
						|
 | 
						|
  constructor({
 | 
						|
    url,
 | 
						|
    username,
 | 
						|
    password,
 | 
						|
    id,
 | 
						|
    children,
 | 
						|
    created_at,
 | 
						|
    updated_at,
 | 
						|
    origin_url,
 | 
						|
    active,
 | 
						|
    early_tracking_seconds,
 | 
						|
    snapshot_at,
 | 
						|
  }) {
 | 
						|
    super(BID_TYPE.API_BID, url);
 | 
						|
 | 
						|
    this.created_at = created_at;
 | 
						|
    this.updated_at = updated_at;
 | 
						|
    this.children = children;
 | 
						|
    this.origin_url = origin_url;
 | 
						|
    this.active = active;
 | 
						|
    this.username = username;
 | 
						|
    this.password = password;
 | 
						|
    this.early_tracking_seconds = early_tracking_seconds;
 | 
						|
    this.snapshot_at = snapshot_at;
 | 
						|
    this.id = id;
 | 
						|
  }
 | 
						|
 | 
						|
  setNewData({
 | 
						|
    url,
 | 
						|
    username,
 | 
						|
    password,
 | 
						|
    id,
 | 
						|
    children,
 | 
						|
    created_at,
 | 
						|
    updated_at,
 | 
						|
    origin_url,
 | 
						|
    active,
 | 
						|
    early_tracking_seconds,
 | 
						|
    snapshot_at,
 | 
						|
  }) {
 | 
						|
    this.created_at = created_at;
 | 
						|
    this.updated_at = updated_at;
 | 
						|
    this.children = children;
 | 
						|
    this.origin_url = origin_url;
 | 
						|
    this.active = active;
 | 
						|
    this.username = username;
 | 
						|
    this.password = password;
 | 
						|
    this.url = url;
 | 
						|
    this.early_tracking_seconds = early_tracking_seconds;
 | 
						|
    this.snapshot_at = snapshot_at;
 | 
						|
  }
 | 
						|
 | 
						|
  puppeteer_connect = async () => {
 | 
						|
    this.browser_context = await browser.createBrowserContext();
 | 
						|
 | 
						|
    const page = await this.browser_context.newPage();
 | 
						|
 | 
						|
    this.page_context = page;
 | 
						|
 | 
						|
    await this.restoreContext();
 | 
						|
  };
 | 
						|
 | 
						|
  async handlePrevListen() {
 | 
						|
    console.log(`👂 [${this.id}] Start handlePrevListen...`);
 | 
						|
 | 
						|
    // Chỉ bắt đầu check khi ảnh đã được chụp
 | 
						|
    if (this.snapshot_at) {
 | 
						|
      const nearestCloseTime = findNearestClosingChild(this);
 | 
						|
 | 
						|
      // Nếu không có roi vào 2 trường hợp
 | 
						|
      // 1 là không có item nào đang hợp lệ
 | 
						|
      // 2 là có item chưa được craw dữ liệu
 | 
						|
 | 
						|
      // Xử lí trường hợp 2
 | 
						|
      if (!nearestCloseTime || this.children.some((item) => !item.close_time)) {
 | 
						|
        console.log(`🔌 [${this.id}] Connecting to puppeteer...`);
 | 
						|
        await this.puppeteer_connect();
 | 
						|
 | 
						|
        console.log(`✅ [${this.id}] Connected. Executing actions...`);
 | 
						|
        await this.action();
 | 
						|
 | 
						|
        console.log(`🎯 [${this.id}] handlePrevListen completed.`);
 | 
						|
        return true;
 | 
						|
      }
 | 
						|
 | 
						|
      // Xử lí trường hợp có item hợp lệ ( close time gần với thời gian hiện tại)
 | 
						|
      const { close_time } = nearestCloseTime;
 | 
						|
      console.log(`📅 [${this.id}] Nearest close_time: ${close_time}`);
 | 
						|
 | 
						|
      const timeToTracking = subtractSeconds(
 | 
						|
        close_time,
 | 
						|
        nearestCloseTime.getEarlyTrackingSeconds() || 0
 | 
						|
      );
 | 
						|
      // const timeToTracking = subtractSeconds(
 | 
						|
      //   close_time,
 | 
						|
      //   this.early_tracking_seconds || 0
 | 
						|
      // );
 | 
						|
 | 
						|
      console.log(
 | 
						|
        `🕰️ [${this.id}] Time to tracking: ${new Date(
 | 
						|
          timeToTracking
 | 
						|
        ).toISOString()}`
 | 
						|
      );
 | 
						|
 | 
						|
      if (!isTimeReached(timeToTracking)) {
 | 
						|
        console.log(`⏳ [${this.id}] Not time to track yet.`);
 | 
						|
        return false;
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    console.log(`🔌 [${this.id}] Connecting to puppeteer...`);
 | 
						|
    await this.puppeteer_connect();
 | 
						|
 | 
						|
    console.log(`✅ [${this.id}] Connected. Executing actions...`);
 | 
						|
    await this.action();
 | 
						|
 | 
						|
    console.log(`🎯 [${this.id}] handlePrevListen completed.`);
 | 
						|
    return true;
 | 
						|
  }
 | 
						|
 | 
						|
  async isLazy() {
 | 
						|
    // Nếu chưa có ảnh chụp working => tab not lazy
 | 
						|
    if (!this.snapshot_at) return false;
 | 
						|
 | 
						|
    // Nếu có một children chưa có thông tin => tab not lazy
 | 
						|
    if (this.children.some((item) => !item.close_time)) return false;
 | 
						|
 | 
						|
    const nearestCloseTime = findNearestClosingChild(this);
 | 
						|
 | 
						|
    // Nếu không có nearest close => tab not lazy
 | 
						|
    if (!nearestCloseTime) return false;
 | 
						|
 | 
						|
    const { close_time } = nearestCloseTime;
 | 
						|
 | 
						|
    const timeToTracking = subtractSeconds(
 | 
						|
      close_time,
 | 
						|
      nearestCloseTime.getEarlyTrackingSeconds() || 0
 | 
						|
    );
 | 
						|
 | 
						|
    // Nếu chưa đến giờ tracking => tab lazy
 | 
						|
    if (!isTimeReached(timeToTracking)) return true;
 | 
						|
 | 
						|
    // Các trường hợp còn lại => not lazy
 | 
						|
    return false;
 | 
						|
  }
 | 
						|
 | 
						|
  listen_events = async () => {
 | 
						|
    if (this.page_context) return;
 | 
						|
 | 
						|
    // await this.puppeteer_connect();
 | 
						|
 | 
						|
    // await this.action();
 | 
						|
 | 
						|
    const results = await this.handlePrevListen();
 | 
						|
 | 
						|
    if (!results) return;
 | 
						|
 | 
						|
    await this.saveContext();
 | 
						|
  };
 | 
						|
 | 
						|
  async saveContext() {
 | 
						|
    if (!this.browser_context || !this.page_context) return;
 | 
						|
 | 
						|
    try {
 | 
						|
      const cookies = await this.browser_context.cookies();
 | 
						|
      const localStorageData = await this.page_context.evaluate(() =>
 | 
						|
        JSON.stringify(localStorage)
 | 
						|
      );
 | 
						|
 | 
						|
      const contextData = {
 | 
						|
        cookies,
 | 
						|
        localStorage: localStorageData,
 | 
						|
      };
 | 
						|
 | 
						|
      const dirPath = path.join(CONSTANTS.PROFILE_PATH);
 | 
						|
 | 
						|
      if (!fs.existsSync(dirPath)) {
 | 
						|
        fs.mkdirSync(dirPath, { recursive: true });
 | 
						|
        console.log(`📂 [${this.id}] Save at folder: ${dirPath}`);
 | 
						|
      }
 | 
						|
 | 
						|
      fs.writeFileSync(
 | 
						|
        path.join(dirPath, sanitizeFileName(this.origin_url) + ".json"),
 | 
						|
        JSON.stringify(contextData, null, 2)
 | 
						|
      );
 | 
						|
      console.log(`✅ [${this.id}] Context saved!`);
 | 
						|
    } catch (error) {
 | 
						|
      console.log(`[${this.id}] Save Context: , ${error.message}`);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  async restoreContext() {
 | 
						|
    if (!this.browser_context || !this.page_context) return;
 | 
						|
 | 
						|
    const filePath = getPathProfile(this.origin_url);
 | 
						|
 | 
						|
    if (!fs.existsSync(filePath)) return;
 | 
						|
 | 
						|
    const contextData = JSON.parse(fs.readFileSync(filePath, "utf8"));
 | 
						|
 | 
						|
    // Restore Cookies
 | 
						|
    await this.page_context.setCookie(...contextData.cookies);
 | 
						|
 | 
						|
    console.log(`🔄 [${this.id}] Context restored!`);
 | 
						|
  }
 | 
						|
 | 
						|
  async onCloseLogin() {}
 | 
						|
 | 
						|
  async isTimeToLogin() {
 | 
						|
    const earlyLoginTime = findEarlyLoginTime(this);
 | 
						|
 | 
						|
    return earlyLoginTime && isTimeReached(earlyLoginTime);
 | 
						|
  }
 | 
						|
 | 
						|
  async saveCodeToLocal({ name, code }) {
 | 
						|
    try {
 | 
						|
      const filePath = getPathLocalData(this.origin_url); // file path
 | 
						|
      const dirPath = path.dirname(filePath); // lấy thư mục cha
 | 
						|
 | 
						|
      // kiểm tra folder chứa file đã tồn tại chưa
 | 
						|
      if (!fs.existsSync(dirPath)) {
 | 
						|
        fs.mkdirSync(dirPath, { recursive: true });
 | 
						|
      }
 | 
						|
 | 
						|
      // ghi file
 | 
						|
      fs.writeFileSync(
 | 
						|
        filePath,
 | 
						|
        JSON.stringify({ name, code, time: Date.now() }, null, 2) // format JSON đẹp
 | 
						|
      );
 | 
						|
    } catch (error) {
 | 
						|
      console.log(
 | 
						|
        `%cerror [${this.id}] models/api-bid.js line:149`,
 | 
						|
        "color: red; display: block; width: 100%;",
 | 
						|
        error
 | 
						|
      );
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  async clearCodeFromLocal() {
 | 
						|
    try {
 | 
						|
      const filePath = getPathLocalData(this.origin_url); // file path
 | 
						|
      const dirPath = path.dirname(filePath); // lấy thư mục cha
 | 
						|
 | 
						|
      // kiểm tra folder chứa file đã tồn tại chưa
 | 
						|
      if (!fs.existsSync(dirPath)) {
 | 
						|
        fs.mkdirSync(dirPath, { recursive: true });
 | 
						|
      }
 | 
						|
 | 
						|
      // ghi file
 | 
						|
      fs.writeFileSync(
 | 
						|
        filePath,
 | 
						|
        JSON.stringify({}, null, 2) // format JSON đẹp
 | 
						|
      );
 | 
						|
    } catch (error) {
 | 
						|
      console.log(
 | 
						|
        `%cerror [${this.id}] models/api-bid.js line:187`,
 | 
						|
        "color: red; display: block; width: 100%;",
 | 
						|
        error
 | 
						|
      );
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  async loadCodeFromLocal() {
 | 
						|
    try {
 | 
						|
      const filePath = getPathLocalData(this.origin_url);
 | 
						|
 | 
						|
      if (!fs.existsSync(filePath)) {
 | 
						|
        console.warn(
 | 
						|
          `%cwarn [${this.id}] models/api-bid.js`,
 | 
						|
          "color: orange; display: block; width: 100%;",
 | 
						|
          `File not found: ${filePath}`
 | 
						|
        );
 | 
						|
        return null; // hoặc {} tùy bạn muốn trả gì
 | 
						|
      }
 | 
						|
 | 
						|
      const fileContent = fs.readFileSync(filePath, "utf-8");
 | 
						|
      const data = JSON.parse(fileContent);
 | 
						|
 | 
						|
      return data; // { name, code, time }
 | 
						|
    } catch (error) {
 | 
						|
      console.error(
 | 
						|
        `%cerror [${this.id}] models/api-bid.js`,
 | 
						|
        "color: red; display: block; width: 100%;",
 | 
						|
        error
 | 
						|
      );
 | 
						|
      return null;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  async isCodeValid(minutes = 8) {
 | 
						|
    try {
 | 
						|
      const data = await this.loadCodeFromLocal();
 | 
						|
      if (!data || !data.time) {
 | 
						|
        return false;
 | 
						|
      }
 | 
						|
 | 
						|
      const now = Date.now();
 | 
						|
      const timeDiff = now - data.time; // tính chênh lệch thời gian (ms)
 | 
						|
 | 
						|
      const validDuration = minutes * 60 * 1000; // phút -> mili giây
 | 
						|
 | 
						|
      return timeDiff <= validDuration;
 | 
						|
    } catch (error) {
 | 
						|
      console.error(
 | 
						|
        `%cerror [${this.id}] models/api-bid.js`,
 | 
						|
        "color: red; display: block; width: 100%;",
 | 
						|
        error
 | 
						|
      );
 | 
						|
      return false;
 | 
						|
    }
 | 
						|
  }
 | 
						|
}
 |