bid-tool/auto-bid-tool/models/langtons.com.au/langtons-product-bid.js

395 lines
17 KiB
JavaScript

import _ from 'lodash';
import { outBid, pushPrice, updateBid } from '../../system/apis/bid.js';
import { sendMessage } from '../../system/apis/notification.js';
import { createOutBidLog } from '../../system/apis/out-bid-log.js';
import configs from '../../system/config.js';
import CONSTANTS from '../../system/constants.js';
import { convertAETtoUTC, removeFalsyValues, takeSnapshot } from '../../system/utils.js';
import { ProductBid } from '../product-bid.js';
export class LangtonsProductBid extends ProductBid {
constructor({ ...prev }) {
super(prev);
}
// Hàm lấy thời gian kết thúc từ trang web
async getCloseTime() {
try {
// Kiểm tra xem có context của trang web không, nếu không thì trả về null
if (!this.page_context) return null;
await this.page_context.waitForSelector('.site-timezone', { timeout: 2000 });
const time = await this.page_context.evaluate(() => {
const el = document.querySelector('.site-timezone');
return el ? el.innerText : null;
});
return time ? convertAETtoUTC(time) : null;
// return new Date(Date.now() + 2 * 60 * 1000).toUTCString();
} catch (error) {
// Nếu có lỗi xảy ra trong quá trình lấy thời gian, trả về null
return null;
}
}
async waitForApiResponse(timeout = 10000) {
if (!this.page_context) {
console.error(`❌ [${this.id}] Error: page_context is undefined.`);
return null;
}
return new Promise((resolve) => {
const onResponse = async (response) => {
try {
if (!response || !response.request().url().includes(configs.WEB_CONFIGS.LANGTONS.API_CALL_TO_TRACKING)) {
return;
}
clearTimeout(timer); // Hủy timeout nếu có phản hồi
this.page_context.off('response', onResponse); // Gỡ bỏ listener
const data = await response.json();
resolve(data);
} catch (error) {
console.error(`❌ [${this.id}] Error while parsing response:`, error?.message);
resolve(null);
}
};
const timer = setTimeout(() => {
console.log(`⏳ [${this.id}] Timeout: No response received within 10s`);
this.page_context.off('response', onResponse); // Gỡ bỏ listener khi timeout
resolve(null);
}, timeout);
this.page_context.on('response', onResponse);
});
}
async getName() {
try {
if (!this.page_context) return null;
await this.page_context.waitForSelector('.product-name', { timeout: 3000 });
return await this.page_context.evaluate(() => {
const el = document.querySelector('.product-name');
return el ? el.innerText : null;
});
} catch (error) {
return null;
}
}
async handleUpdateBid({ lot_id, close_time, name, current_price, reserve_price, model }) {
const response = await updateBid(this.id, { lot_id, close_time, name, current_price, reserve_price: Number(reserve_price) || 0, model });
if (response) {
this.lot_id = response.lot_id;
this.close_time = response.close_time;
this.start_bid_time = response.start_bid_time;
}
}
update = async () => {
if (!this.page_context) return;
console.log(`🔄 [${this.id}] Call update for ID: ${this.id}`);
// 📌 Lấy thời gian kết thúc đấu giá từ giao diện
const close_time = await this.getCloseTime();
console.log(`⏳ [${this.id}] Retrieved close time: ${close_time}`);
// 📌 Lấy tên sản phẩm hoặc thông tin liên quan
const name = await this.getName();
console.log(`📌 [${this.id}] Retrieved name: ${name}`);
// 📌 Chờ phản hồi API từ trang, tối đa 10 giây
const result = await this.waitForApiResponse();
// 📌 Nếu không có dữ liệu trả về thì dừng
if (!result) {
console.log(`⚠️ [${this.id}] No valid data received, skipping update.`);
return;
}
// 📌 Loại bỏ các giá trị không hợp lệ và bổ sung thông tin cần thiết
const data = removeFalsyValues(
{
model: result?.pid || null,
lot_id: result?.lotId || null,
reserve_price: result.lotData?.minimumBid || null,
current_price: result.lotData?.currentMaxBid || null,
// close_time: close_time && !this.close_time ? String(close_time) : null,
close_time: close_time ? String(close_time) : null,
name,
},
['close_time'],
);
console.log(`🚀 [${this.id}] Processed data ready for update`);
// 📌 Gửi dữ liệu cập nhật lên hệ thống
await this.handleUpdateBid(data);
console.log('✅ Update successful!');
return { ...response, name, close_time };
};
async getContinueShopButton() {
try {
if (!this.page_context) return null;
await this.page_context.waitForSelector('.btn.btn-block.btn-primary.error.continue-shopping', { timeout: 3000 });
return await this.page_context.evaluate(() => {
const el = document.querySelector('.btn.btn-block.btn-primary.error.continue-shopping');
return el;
});
} catch (error) {
return null;
}
}
async handlePlaceBid() {
if (!this.page_context) {
console.log(`⚠️ [${this.id}] No page context found, aborting bid process.`);
return;
}
const page = this.page_context;
if (global[`IS_PLACE_BID-${this.id}`]) {
console.log(`⚠️ [${this.id}] Bid is already in progress, skipping.`);
return;
}
try {
console.log(`🔄 [${this.id}] Starting bid process...`);
global[`IS_PLACE_BID-${this.id}`] = true;
const continueShopBtn = await this.getContinueShopButton();
if (continueShopBtn) {
console.log(`⚠️ [${this.id}] Outbid detected, calling outBid function.`);
await outBid(this.id);
return;
}
// Kiểm tra nếu giá hiện tại lớn hơn giá tối đa cộng thêm giá cộng thêm
if (this.current_price > this.max_price + this.plus_price) {
console.log(`⚠️ [${this.id}] Outbid bid`); // Ghi log cảnh báo nếu giá hiện tại vượt quá mức tối đa cho phép
return; // Dừng hàm nếu giá đã vượt qua giới hạn
}
// Đợi phản hồi từ API
const response = await this.waitForApiResponse();
// Kiểm tra nếu phản hồi không tồn tại hoặc nếu giá đấu của người dùng bằng với giá tối đa hiện tại
if (!response || (response?.lotData?.myBid && response.lotData.myBid == this.max_price) || response?.lotData?.minimumBid > this.max_price) {
console.log(`⚠️ [${this.id}] No response or myBid equals max_price:`, response); // Ghi log nếu không có phản hồi hoặc giá đấu của người dùng bằng giá tối đa
return; // Nếu không có phản hồi hoặc giá đấu bằng giá tối đa thì dừng hàm
}
// Kiểm tra nếu dữ liệu trong response có tồn tại và trạng thái đấu giá (bidStatus) không phải là 'None'
if (response.lotData && response.lotData?.bidStatus !== 'None' && this.max_price == response?.lotData.myBid) {
console.log(`✔️ [${this.id}] Bid status is not 'None'. Current bid status:`, response.lotData?.bidStatus); // Ghi log nếu trạng thái đấu giá không phải 'None'
return; // Nếu trạng thái đấu giá không phải là 'None', dừng hàm
}
const bidHistoriesItem = _.maxBy(this.histories, 'price');
console.log(`📜 [${this.id}] Current bid history:`, this.histories);
if (bidHistoriesItem && bidHistoriesItem.price === this.current_price && this.max_price == response?.lotData.myBid) {
console.log(`🔄 [${this.id}] You have already bid on this item! (Bid Price: ${bidHistoriesItem.price})`);
return;
}
console.log(`💰 [${this.id}] Placing a bid with amount: ${this.reserve_price}`);
// 📌 Làm rỗng ô input trước khi nhập giá đấu
await page.evaluate(() => {
document.querySelector('#place-bid').value = '';
});
console.log(`📝 [${this.id}] Cleared bid input field.`);
// 📌 Nhập giá đấu vào ô input
await page.type('#place-bid', String(this.max_price), { delay: 100 });
console.log(`✅ [${this.id}] Entered bid amount: ${this.max_price}`);
// 📌 Lấy giá trị thực tế từ ô input sau khi nhập
const bidValue = await page.evaluate(() => document.querySelector('#place-bid').value);
console.log(`🔍 Entered bid value: ${bidValue}`);
// 📌 Kiểm tra nếu giá trị nhập vào không khớp với giá trị mong muốn
if (!bidValue || bidValue !== String(this.max_price)) {
console.log(`❌ Incorrect bid amount! Received: ${bidValue}`);
return; // Dừng thực hiện nếu giá trị nhập sai
}
// 📌 Nhấn nút "Place Bid"
await page.click('.place-bid-submit .btn.btn-primary.btn-block.place-bid-btn', { delay: 10000 });
console.log(`🖱️ [${this.id}] Clicked "Place Bid" button.`);
console.log(`📩 [${this.id}] Bid submitted, waiting for navigation...`);
// 📌 Chờ trang load lại để cập nhật trạng thái đấu giá
await page.waitForNavigation({ timeout: 8000, waitUntil: 'domcontentloaded' });
console.log(`🔄 [${this.id}] Page reloaded, checking bid status...`);
const { lotData } = await this.waitForApiResponse();
console.log(`📡 [${this.id}] API Response received:`, lotData);
// 📌 Kiểm tra trạng thái đấu giá từ API
if (lotData?.myBid == this.max_price) {
console.log(`📸 [${this.id}] Taking bid success snapshot...`);
await takeSnapshot(page, this, 'bid-success', CONSTANTS.TYPE_IMAGE.SUCCESS);
sendMessage(this);
console.log(`✅ [${this.id}] Bid placed successfully!`);
return;
}
console.log(`⚠️ [${this.id}] Bid action completed, but status is still "None".`);
} catch (error) {
console.log(`🚨 [${this.id}] Error placing bid: ${error.message}`);
} finally {
console.log(`🔚 [${this.id}] Resetting bid flag.`);
global[`IS_PLACE_BID-${this.id}`] = false;
}
}
async handleCreateLogsOnServer(data) {
const values = data.map((item) => {
return {
model: item.pid,
lot_id: item.lotId,
out_price: item.lotData.minimumBid || 0,
raw_data: JSON.stringify(item),
};
});
await createOutBidLog(values);
}
async gotoLink() {
const page = this.page_context;
if (page.isClosed()) {
console.error(`❌ [${this.id}] Page has been closed, cannot navigate.`);
return;
}
console.log(`🔄 [${this.id}] Starting the bidding process...`);
try {
console.log(`🌐 [${this.id}] Navigating to: ${this.url} ...`);
await page.goto(this.url, { waitUntil: 'networkidle2' });
console.log(`✅ [${this.id}] Successfully navigated to: ${this.url}`);
console.log(`🖥️ [${this.id}] Bringing tab to the foreground...`);
await page.bringToFront();
console.log(`🛠️ [${this.id}] Setting custom user agent...`);
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}] Listening for API responses...`);
// // 🔥 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(configs.WEB_CONFIGS.LANGTONS.API_CALL_TO_TRACKING)) {
// console.log('🚀 Fake response cho request:', request.url());
// const fakeData = fs.readFileSync('./data/fake-out-lot-langtons.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);
// }
// }
// });
const onResponse = async (response) => {
const url = response?.request()?.url();
if (!url || !url.includes(configs.WEB_CONFIGS.LANGTONS.API_CALL_TO_TRACKING)) {
return;
}
try {
const { lotData, ...prev } = await response.json();
console.log(`📜 [${this.id}] Received lotData:`, lotData);
if (!lotData || lotData.lotId !== this.lot_id) {
console.log(`⚠️ [${this.id}] Ignored response for lotId: ${lotData?.lotId}`);
return;
}
console.log(`🔍 [${this.id}] Checking bid status...`);
if (['Outbid'].includes(lotData?.bidStatus)) {
console.log(`⚠️ [${this.id}] Outbid detected, attempting to place a new bid...`);
this.handleCreateLogsOnServer([{ lotData, ...prev }]);
} else if (['Winning'].includes(lotData?.bidStatus)) {
const bidHistoriesItem = _.maxBy(this.histories, 'price');
if (!bidHistoriesItem || bidHistoriesItem?.price != lotData?.currentMaxBid) {
pushPrice({
bid_id: this.id,
price: lotData?.currentMaxBid,
});
}
}
if (lotData.myBid && this.max_price != lotData.myBid) {
this.handlePlaceBid();
}
} catch (error) {
console.error(`🚨 [${this.id}] Error parsing API response:`, error);
}
};
console.log(`🔄 [${this.id}] Removing previous response listeners...`);
this.page_context.off('response', onResponse);
console.log(`📡 [${this.id}] Attaching new response listener...`);
this.page_context.on('response', onResponse);
console.log(`✅ [${this.id}] Navigation setup complete.`);
} catch (error) {
console.error(`❌ [${this.id}] Error during navigation:`, error);
}
}
action = async () => {
try {
const page = this.page_context;
// 📌 Kiểm tra nếu trang chưa tải đúng URL thì điều hướng đến URL mục tiêu
if (!page.url() || !page.url().includes(this.url)) {
console.log(`🔄 [${this.id}] Navigating to target URL: ${this.url}`);
await this.gotoLink();
}
await this.handlePlaceBid();
} catch (error) {
console.error(`🚨 [${this.id}] Error navigating the page: ${error}`);
}
};
}