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

573 lines
18 KiB
JavaScript

import _ from "lodash";
import { pushPrice, updateBid } from "../../system/apis/bid.js";
import configs from "../../system/config.js";
import { delay, isTimeReached, removeFalsyValues } from "../../system/utils.js";
import { ProductBid } from "../product-bid.js";
import { sendMessage } from "../../system/apis/notification.js";
import axios from "../../system/axios.js";
export class PicklesProductBid extends ProductBid {
constructor({ ...prev }) {
super(prev);
}
async handleUpdateBid({
lot_id,
close_time,
name,
current_price,
reserve_price,
}) {
const response = await updateBid(this.id, {
lot_id,
close_time,
name,
current_price,
reserve_price: Number(reserve_price) || 0,
});
if (response) {
this.lot_id = response.lot_id;
this.close_time = response.close_time;
this.start_bid_time = response.start_bid_time;
}
}
fetchFromPage = async (url) => {
return await this.page_context.evaluate(async (url) => {
try {
const res = await fetch(url, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
return await res.json();
} catch (err) {
return { error: err.message };
}
}, url);
};
detailData = async () => {
return await this.fetchFromPage(
configs.WEB_CONFIGS.PICKLES.API_DETAIL_PRODUCT(this.model)
);
};
getName = async () => {
if (!this.page_context) return null;
try {
return await this.page_context.$eval(
"#pd-ph-header > div:first-child > div > div:nth-child(2) > div > h1",
(el) => el.textContent
);
} catch (error) {
console.log(
"%cmodels/pickles.com.au/pickles-product-bid.js:60 error.message",
"color: #007acc;",
error.message
);
return null;
}
};
update = async () => {
try {
if (!this.page_context) return;
const result = await this.detailData();
if (!result || !result[0]) return;
const { item, bidding } = result[0];
const name = await this.getName();
console.log(
"%cmodels/pickles.com.au/pickles-product-bid.js:83 item",
"color: #007acc;",
bidding
);
// 📌 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(
{
lot_id: String(item?.id) || null,
reserve_price: bidding?.minimumBidAmount || null,
current_price: bidding?.currentActualBid || null,
close_time: new Date(item?.itemBidEndTimestamp).toUTCString() || null,
name: name || null,
},
["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);
await this.page_context.reload({ waitUntil: "networkidle2" });
await this.page_context.waitForNavigation({
timeout: 8000,
waitUntil: "domcontentloaded",
});
} catch (error) {
console.log("Error Update", error.message);
}
};
handlePlaceBidLive() {
return new Promise(async (resolve, reject) => {
if (!this.page_context || !this.lot_id) {
console.log(`[${this.id}] Page context or model is missing.`);
reject("Context is not define");
return;
}
try {
console.log(`💰 [${this.id}] Prepared Bid Amount: ${this.max_price}`);
const result = await this.page_context.evaluate(
async (bidAmount, lotRef, url) => {
const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
itemId: lotRef,
bidValues: {
activity: "BID",
maxBid: bidAmount, // giá trị tối đa của sản phẩm
roundedMaxBid: bidAmount, // giá trị tối đa của sản phẩm
submittedBuyNowValue: null,
},
buyerFeeCalculated: false,
buyerFees: null,
dashboardRedirectUrl: null,
itemTitle: null,
productLine: null,
registrationRequired: false,
totalAmount: null,
updateDetailsRequired: null,
}),
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return await response.json();
},
this.max_price + this.plus_price,
this.lot_id,
configs.WEB_CONFIGS.PICKLES.API_CHECKOUT
);
console.log("🧾 API Bid Result:", {
bid_amount: this.max_price + this.plus_price,
result,
});
if (!result?.confirmationRequest) reject("Api call failure");
resolve(result);
} catch (err) {
console.log(`[${this.id}] Failed to submit bid: ${err.message}`);
reject(err);
}
});
}
async handlePlaceBid() {
// Kiểm tra xem có page context không, nếu không có thì kết thúc quá trình đấu giá
if (!this.page_context) {
console.log(
`⚠️ [${this.id}] No page context found, aborting bid process.`
);
return;
}
const page = this.page_context;
// Kiểm tra xem đấu giá đã đang diễn ra chưa. Nếu có thì không thực hiện nữa
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...`);
// Đánh dấu rằng đang thực hiện quá trình đấu giá để tránh đấu lại
global[`IS_PLACE_BID-${this.id}`] = true;
// Kiểm tra xem giá hiện tại có vượt qua mức giá tối đa chưa
if (this.current_price > this.max_price + this.plus_price) {
console.log(`⚠️ [${this.id}] Outbid bid`);
return; // Nếu giá hiện tại vượt quá mức giá tối đa thì dừng lại
}
// Kiểm tra thời gian đấu giá
if (this.start_bid_time && !isTimeReached(this.start_bid_time)) {
console.log(
`⏳ [${this.id}] Not yet time to bid. Skipping Product: ${
this.name || "None"
}`
);
return; // Nếu chưa đến giờ đấu giá thì bỏ qua
}
// Đợi lấy thông tin API để kiểm tra tình trạng đấu giá hiện tại
const response = await this.detailData();
if (!response) {
console.log(`[${this.id}] Can't get info data`);
return;
}
const { bidding } = response[0];
console.log(
"%cmodels/pickles.com.au/pickles-product-bid.js:157 response",
"color: #007acc;",
bidding
);
// // Kiểm tra nếu có lý do nào khiến không thể tiếp tục đấu giá
const shouldStop =
!response ||
bidding?.currentActualBid > this.max_price + this.plus_price ||
(bidding?.userItemBidStatus &&
bidding?.userItemBidStatus?.type !== "OUTBID") ||
!bidding?.minimumBidAmount ||
bidding.minimumBidAmount > this.max_price + this.plus_price;
if (shouldStop) {
console.log(`⚠️ [${this.id}] Stop bidding:`, {
reservePrice: bidding?.minimumBidAmount,
currentBidAmount: response?.currentBidAmount,
maxBidAmount: response?.maxBidAmount,
});
return; // Nếu gặp điều kiện dừng thì không thực hiện đấu giá
}
// Tìm bid history lớn nhất từ các lịch sử đấu giá của item
const bidHistoriesItem = _.maxBy(this.histories, "price");
console.log(`📜 [${this.id}] Current bid history:`, this.histories);
// Kiểm tra xem đã bid rồi chưa. Nếu đã bid rồi thì bỏ qua
if (
bidHistoriesItem &&
bidHistoriesItem?.price == this.current_price &&
this.max_price + this.plus_price == response?.maxBidAmount
) {
console.log(
`🔄 [${this.id}] You have already bid on this item! (Bid Price: ${bidHistoriesItem?.price})`
);
return;
}
if (this.reserve_price <= 0) {
console.log(`[${this.reserve_price}]`);
return;
}
console.log(
`===============Start call to submit [${this.id}] ================`
);
// waiting 2s
await delay(2000);
// Nếu chưa bid, thực hiện đặt giá
console.log(
`💰 [${this.id}] Placing a bid with amount: ${this.max_price}`
);
// Gửi bid qua API và nhận kết quả
let result = null;
if (this.isSandbox()) {
result = await this.handleCallActionSanbox();
} else {
result = await this.handlePlaceBidLive();
}
console.log({ result });
// Nếu không có kết quả (lỗi khi gửi bid) thì dừng lại
if (!result || !result?.confirmationRequest) {
console.log(
"%cmodels/pickles.com.au/pickles-product-bid.js:289 Error when call plance bid",
"color: #007acc;",
"Error when call plance bid"
);
return;
}
// Gửi thông báo đã đấu giá thành công
// sendMessage(this);
pushPrice({
bid_id: this.id,
price: result?.yourBid || 0,
});
await this.page_context.evaluate(() => location.reload());
console.log(`✅ [${this.id}] Bid placed successfully!`);
} catch (error) {
// Nếu có lỗi xảy ra trong quá trình đấu giá, log lại lỗi
console.log(error);
console.log(`🚨 [${this.id}] Error placing bid: ${error.message}`);
} finally {
// Đảm bảo luôn reset trạng thái đấu giá sau khi hoàn thành
console.log(`🔚 [${this.id}] Resetting bid flag.`);
global[`IS_PLACE_BID-${this.id}`] = false;
}
}
async handlePlaceBidSanbox() {
return new Promise(async (resolve, reject) => {
if (!this.page_context || !this.lot_id) {
console.log(`[${this.id}] Page context or model is missing.`);
reject("Context is not define");
return;
}
try {
console.log(`💰 [${this.id}] Prepared Bid Amount: ${this.max_price}`);
const result = await this.page_context.evaluate(
async (lotRef, url) => {
const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
itemId: lotRef,
bidValues: {
activity: "BID",
maxBid: 0, // giá trị tối đa của sản phẩm
roundedMaxBid: 0, // giá trị tối đa của sản phẩm
submittedBuyNowValue: null,
},
buyerFeeCalculated: false,
buyerFees: null,
dashboardRedirectUrl: null,
itemTitle: null,
productLine: null,
registrationRequired: false,
totalAmount: null,
updateDetailsRequired: null,
}),
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return await response.json();
},
this.lot_id,
configs.WEB_CONFIGS.PICKLES.API_CHECKOUT
);
console.log("🧾 API Bid Result:", {
bid_amount: this.max_price + this.plus_price,
result,
});
resolve(result);
} catch (err) {
console.log(`[${this.id}] Failed to submit bid: ${err.message}`);
reject(err);
}
});
}
isOutBid = async () => {
try {
// Chờ tối đa 10s cho element xuất hiện
const element = await page.waitForSelector(
'[data-testid="pd-pbsb-bit-status-banner"]',
{
timeout: 10000, // 10s
visible: true, // chỉ accept khi element hiện ra
}
);
if (!element) return false; // không có thì return false
// Lấy innerHTML của element
const innerHTML = await page.evaluate((el) => el.innerHTML, element);
console.log(
"%cmodels/pickles.com.au/pickles-product-bid.js:339 {in}",
"color: #007acc;",
{ innerHTML }
);
// Kiểm tra có từ "outbid" không
return innerHTML.includes("outbid");
} catch (error) {
// Nếu lỗi (timeout hoặc gì đó) => return false
return false;
}
};
async trackingOutbid() {
if (!this.page_context) return;
if (global[`TRACKING_PROCRESS_${this.id}`]) {
console.log(`🔄 [${this.id}] Removing previous response listeners...`);
clearInterval(global[`TRACKING_PROCRESS_${this.id}`]);
}
try {
global[`TRACKING_PROCRESS_${this.id}`] = setInterval(async () => {
try {
const result = await this.detailData();
if (!result) return;
console.log(`📈 [${this.id}] Bid data: `, result);
const { item, bidding } = result[0];
console.log(
`📊 [${this.id}] API Info - minimumBidAmount: ${bidding?.minimumBidAmount}, currentActualBid: ${bidding?.currentActualBid}`
);
// Lấy giá reverse (giá thấp nhất cần để thắng đấu giá)
const reversePrice = bidding?.currentActualBid;
const currentBidAmount = bidding?.currentActualBid;
const maxBidAmount = bidding?.buyerCurrentBid?.maximumBid;
console.log(`💰 [${this.id}] Current reverse price: ${reversePrice}`);
// Tìm ra lịch sử đấu giá có giá cao nhất trong this.histories
const bidHistoriesItem = _.maxBy(this.histories, "price");
console.log(
`📈 [${this.id}] Highest local bid: ${
bidHistoriesItem?.price ?? "N/A"
}`
);
if (!this.close_time || !this.lot_id || !this.current_price) return;
// Nếu chưa từng đặt giá và có giá tối đa (maxBidAmount), thì push giá đó vào histories
if (
(!bidHistoriesItem && maxBidAmount) ||
(bidHistoriesItem?.price != currentBidAmount &&
currentBidAmount == maxBidAmount)
) {
console.log(
`🆕 [${this.id}] No previous bid found. Placing initial bid at ${maxBidAmount}.`
);
pushPrice({
bid_id: this.id,
price: Number(currentBidAmount),
});
}
// Nếu giá hiện tại cao hơn giá mình đã đặt, và reversePrice vẫn trong giới hạn cho phép, và đang bị outbid thì sẽ đặt giá tiếp
if (
reversePrice <= this.max_price + this.plus_price &&
currentBidAmount <= this.max_price + this.plus_price &&
this.max_price != maxBidAmount &&
this.histories.length > 0 &&
bidding?.currentMaximumBid !== this.max_price + this.plus_price
) {
console.log(
`⚠️ [${this.id}] Outbid detected. Reverse price acceptable. Placing a new bid...`
);
await this.handlePlaceBid();
} else {
console.log(`✅ [${this.id}] No bid needed. Conditions not met.`);
}
} catch (error) {
console.error(`🚨 [${this.id}] Error parsing API response:`, error);
}
}, 5000);
console.log(`✅ [${this.id}] Navigation setup complete.`);
} catch (error) {
console.error(`❌ [${this.id}] Error during navigationnnnn:`, error);
}
}
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...`);
// tracking out bid
this.trackingOutbid();
} 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}`);
}
};
async handleCallActionSanbox() {
try {
const result = await this.handlePlaceBidSanbox();
await axios({
url: this.ACTION_URL({ type: "api" }),
data: {
id: this.id,
data: JSON.stringify(result),
},
method: "POST",
});
if (global[`TRACKING_PROCRESS_${this.id}`]) {
clearInterval(global[`TRACKING_PROCRESS_${this.id}`]);
}
await this.close();
return result;
} catch (error) {
console.log("a:", error);
}
}
}