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); if (!nearestCloseTime) { console.log(`❌ [${this.id}] No nearest closing child found.`); return false; } const { close_time } = nearestCloseTime; console.log(`📅 [${this.id}] Nearest close_time: ${close_time}`); 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; const nearestCloseTime = findNearestClosingChild(this); // Nếu không có nearest close => tab lazy if (!nearestCloseTime) return true; const { close_time } = nearestCloseTime; const timeToTracking = subtractSeconds( close_time, this.early_tracking_seconds || 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; } } }