334 lines
8.7 KiB
JavaScript
334 lines
8.7 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);
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
|
|
// 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,
|
|
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;
|
|
}
|
|
}
|
|
}
|