267 lines
6.8 KiB
JavaScript
267 lines
6.8 KiB
JavaScript
import * as fs from "fs";
|
|
import * as path from "path";
|
|
import { PuppeteerScreenRecorder } from "puppeteer-screen-recorder";
|
|
import { outBid, uploadRecord } from "../system/apis/bid.js";
|
|
import BID_TYPE from "../system/bid-type.js";
|
|
import browser from "../system/browser.js";
|
|
import CONSTANTS from "../system/constants.js";
|
|
import { getPathProfile, randomDelayWithMeta } from "../system/utils.js";
|
|
import { Bid } from "./bid.js";
|
|
import { unlink } from "fs/promises";
|
|
export class ProductBid extends Bid {
|
|
// value 'live' | 'sandbox'
|
|
static MODE_KEY = "mode_key";
|
|
|
|
// value is minutes esg: arrival_offset_seconds of web bid parent
|
|
static ARRIAVAL_OFFSET_SECONDS_LIVE = "arrival_offset_seconds_live";
|
|
static ARRIAVAL_OFFSET_SECONDS_SANDBOX = "arrival_offset_seconds_sandbox";
|
|
|
|
// value is minutes esg: early_tracking_seconds of web bid parent
|
|
static EARLY_TRACKING_SECONDS = "early_tracking_seconds";
|
|
static EARLY_TRACKING_SECONDS_LIVE = `${this.EARLY_TRACKING_SECONDS}_live`;
|
|
static EARLY_TRACKING_SECONDS_SANDBOX = `${this.EARLY_TRACKING_SECONDS}_sandbox`;
|
|
|
|
id;
|
|
max_price;
|
|
model;
|
|
lot_id;
|
|
plus_price;
|
|
close_time;
|
|
first_bid;
|
|
quantity;
|
|
created_at;
|
|
updated_at;
|
|
histories;
|
|
start_bid_time;
|
|
parent_browser_context;
|
|
web_bid;
|
|
current_price;
|
|
name;
|
|
reserve_price;
|
|
update;
|
|
metadata;
|
|
recorder;
|
|
name_record;
|
|
|
|
constructor({
|
|
url,
|
|
max_price,
|
|
plus_price,
|
|
model,
|
|
first_bid = false,
|
|
id,
|
|
created_at,
|
|
updated_at,
|
|
quantity = 1,
|
|
histories = [],
|
|
close_time,
|
|
lot_id,
|
|
start_bid_time,
|
|
web_bid,
|
|
current_price,
|
|
reserve_price,
|
|
name,
|
|
metadata,
|
|
}) {
|
|
super(BID_TYPE.PRODUCT_TAB, url);
|
|
this.max_price = max_price || 0;
|
|
this.model = model;
|
|
this.plus_price = plus_price || 0;
|
|
this.first_bid = first_bid;
|
|
this.id = id;
|
|
this.created_at = created_at;
|
|
this.updated_at = updated_at;
|
|
this.quantity = quantity;
|
|
this.histories = histories;
|
|
this.close_time = close_time;
|
|
this.lot_id = lot_id;
|
|
this.start_bid_time = start_bid_time;
|
|
this.web_bid = web_bid;
|
|
this.current_price = current_price;
|
|
this.name = name;
|
|
this.reserve_price = reserve_price;
|
|
this.metadata = metadata;
|
|
}
|
|
|
|
setNewData({
|
|
url,
|
|
max_price,
|
|
plus_price,
|
|
model,
|
|
first_bid = false,
|
|
id,
|
|
created_at,
|
|
updated_at,
|
|
quantity = 1,
|
|
histories = [],
|
|
close_time,
|
|
lot_id,
|
|
start_bid_time,
|
|
web_bid,
|
|
current_price,
|
|
reserve_price,
|
|
name,
|
|
metadata,
|
|
}) {
|
|
this.max_price = max_price || 0;
|
|
this.model = model;
|
|
this.plus_price = plus_price || 0;
|
|
this.first_bid = first_bid;
|
|
this.id = id;
|
|
this.created_at = created_at;
|
|
this.updated_at = updated_at;
|
|
this.quantity = quantity;
|
|
this.histories = histories;
|
|
this.close_time = close_time;
|
|
this.lot_id = lot_id;
|
|
this.start_bid_time = start_bid_time;
|
|
this.web_bid = web_bid;
|
|
this.url = url;
|
|
this.current_price = current_price;
|
|
this.name = name;
|
|
this.reserve_price = reserve_price;
|
|
this.metadata = metadata;
|
|
}
|
|
|
|
puppeteer_connect = async () => {
|
|
if (!this.parent_browser_context) {
|
|
console.log(
|
|
`❌ Connect fail. parent_browser_context is null: ${this.id}`
|
|
);
|
|
return;
|
|
}
|
|
|
|
const context = await browser.createBrowserContext();
|
|
|
|
const statusInit = await this.restoreContext(context);
|
|
|
|
if (!statusInit) {
|
|
console.log(`⚠️ Restore failed.`);
|
|
return;
|
|
}
|
|
|
|
const page = await context.newPage();
|
|
|
|
this.page_context = page;
|
|
this.browser_context = context;
|
|
};
|
|
|
|
async restoreContext(context) {
|
|
const filePath = getPathProfile(this.web_bid.origin_url);
|
|
|
|
if (!fs.existsSync(filePath)) return false;
|
|
|
|
const contextData = JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
|
|
// Restore Cookies
|
|
await context.setCookie(...contextData.cookies);
|
|
|
|
return true;
|
|
}
|
|
|
|
async gotoLink() {
|
|
const page = this.page_context;
|
|
|
|
if (page.isClosed()) {
|
|
console.error("❌ Page has been closed, cannot navigate.");
|
|
return;
|
|
}
|
|
|
|
console.log("🔄 Starting the bidding process...");
|
|
|
|
try {
|
|
await page.goto(this.url, { waitUntil: "networkidle2" });
|
|
console.log(`✅ Navigated to: ${this.url}`);
|
|
|
|
await page.bringToFront();
|
|
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("👀 Brought the tab to the foreground.");
|
|
} catch (error) {
|
|
console.error("❌ Error during navigation:", error);
|
|
}
|
|
}
|
|
|
|
getMode = () => {
|
|
return (
|
|
this.metadata.find((item) => item.key_name === ProductBid.MODE_KEY)
|
|
?.value || "live"
|
|
);
|
|
};
|
|
|
|
getEarlyTrackingSeconds = () => {
|
|
const mode = this.getMode();
|
|
|
|
return (
|
|
this.metadata.find(
|
|
(item) =>
|
|
item.key_name === `${ProductBid.EARLY_TRACKING_SECONDS}_${mode}`
|
|
)?.value || this.web_bid.early_tracking_seconds
|
|
);
|
|
};
|
|
|
|
isSandbox() {
|
|
return this.getMode() !== "live";
|
|
}
|
|
|
|
async delayForAction() {
|
|
// Thực thi hành động
|
|
console.log(`[${this.id}] 🚀 Executing action`);
|
|
const { wait, delay } = randomDelayWithMeta();
|
|
|
|
console.log(
|
|
`[${this.id}] ⏳ Delay for action: ${(delay / 1000).toFixed(2)} seconds`
|
|
);
|
|
|
|
await wait;
|
|
|
|
console.log(`[${this.id}] ✅ Finished delay`);
|
|
}
|
|
|
|
async close() {
|
|
await outBid(this.id);
|
|
}
|
|
|
|
async startRecordSandbox() {
|
|
if (
|
|
!this.page_context ||
|
|
!this.name ||
|
|
this.recorder ||
|
|
this.getMode() === "live"
|
|
)
|
|
return;
|
|
|
|
const dirPath = CONSTANTS.RECORD_VIDEO_PATH;
|
|
|
|
// 📁 Kiểm tra và tạo thư mục nếu chưa có
|
|
if (!fs.existsSync(dirPath)) {
|
|
fs.mkdirSync(dirPath, { recursive: true }); // recursive để tạo nested folder nếu cần
|
|
console.log("📁 Created recording folder:", dirPath);
|
|
}
|
|
|
|
this.name_record = `${this.id}_${this.name}_${new Date().getTime()}.mp4`;
|
|
|
|
const filePath = path.join(dirPath, this.name_record);
|
|
|
|
this.recorder = new PuppeteerScreenRecorder(this.page_context);
|
|
await this.recorder.start(filePath);
|
|
}
|
|
|
|
async stopRecordSandbox() {
|
|
if (!this.recorder || this.getMode() === "live") return;
|
|
|
|
await this.recorder.stop();
|
|
|
|
const filePath = path.join(CONSTANTS.RECORD_VIDEO_PATH, this.name_record);
|
|
|
|
const result = await uploadRecord(this, filePath);
|
|
|
|
return result;
|
|
}
|
|
|
|
ACTION_URL(options = { type: "action" }) {
|
|
return `${process.env.BASE_URL}bids/hook-action?id=${this.id}&type=${options.type}`;
|
|
}
|
|
}
|