bid-tool/auto-bid-tool/models/grays.com/grays-product-bid.js

512 lines
14 KiB
JavaScript

import {
pushPrice,
updateBid,
updateStatusByPrice,
} from "../../system/apis/bid.js";
import axios from "../../system/axios.js";
import {
delay,
extractNumber,
isTimeReached,
removeFalsyValues,
} from "../../system/utils.js";
import { ProductBid } from "../product-bid.js";
export class GraysProductBid extends ProductBid {
constructor({ ...prev }) {
super(prev);
}
getCloseTime = async () => {
try {
if (!this.page_context) return null;
await this.page_context.waitForSelector("#lot-closing-datetime", {
timeout: 3000,
});
return await this.page_context.$eval(
"#lot-closing-datetime",
(el) => el.value
);
} catch (error) {
return null;
}
};
getPriceWasBid = async () => {
try {
if (!this.page_context) return null;
await this.page_context.waitForSelector(
"#biddableLot form div div:nth-child(1) span span",
{ timeout: 3000 }
);
const element = await this.page_context.$(
"#biddableLot form div div:nth-child(1) span span"
);
const textPrice = await this.page_context.evaluate(
(el) => el.textContent,
element
);
return extractNumber(textPrice) || null;
} catch (error) {
return null;
}
};
async isCloseProduct() {
const close_time = await this.getCloseTime();
const currentUrl = await this.page_context.url();
if (currentUrl !== this.url) {
return { result: false, close_time };
}
if (!close_time) {
const priceWasBid = await this.getPriceWasBid();
await updateStatusByPrice(this.id, priceWasBid);
return { result: true, close_time: null };
}
await delay(500);
if (!close_time || new Date(close_time).getTime() <= new Date().getTime()) {
console.log(`❌ [${this.id}] Product is close ${close_time}`);
return { result: true, close_time };
}
return { result: false, close_time };
}
async placeBid() {
try {
await this.page_context.evaluate(() => {
document.querySelector("#price").value = "";
});
console.log("✅ Cleared price");
await this.page_context.type("#price", String(this.max_price));
console.log("✅ Typed max price");
await delay(5000);
const currentValue = await this.page_context.$eval(
"#price",
(el) => el.value
);
console.log("✅ Checked currentValue:", currentValue);
if (currentValue !== String(this.max_price)) {
console.warn(
`[${this.id}] Value not match #price: ${currentValue} !== ${this.max_price}`
);
return;
}
await this.page_context.click("#btnSubmit");
console.log("✅ Clicked submit");
await delay(1000);
await this.page_context.waitForSelector("button", { timeout: 5000 });
await delay(5000);
// await this.page_context.click("button");
// await this.page_context.waitForNavigation({ timeout: 5000 });
// await this.page_context.waitForFunction(
// () => document.body.innerText.includes("Successfully"),
// { timeout: 5000 } // hoặc lâu hơn nếu cần
// );
// console.log("✅ Found 'Successfully'");
// await pushPrice({
// bid_id: this.id,
// price: this.max_price,
// });
// await this.handleReturnProductPage();
if (this.isSandbox()) {
await this.handlePlaceBidSandbox();
} else {
await this.handlePlaceBidLive();
}
return true;
} catch (error) {
await this.page_context.goto(this.url);
console.log(error);
console.log(`❌ [${this.id}] Error in placeBid: ${error.message}`);
return false;
} finally {
global.IS_CLEANING = true;
}
}
async handlePlaceBidLive() {
try {
global.IS_CLEANING = false;
await this.page_context.click("button");
await this.page_context.waitForNavigation({ timeout: 5000 });
// await this.page_context.waitForFunction(
// () => document.body.innerText.includes("Successfully"),
// { timeout: 5000 } // hoặc lâu hơn nếu cần
// );
console.log("✅ Found 'Successfully'");
await pushPrice({
bid_id: this.id,
price: this.max_price,
});
await this.handleReturnProductPage();
} finally {
global.IS_CLEANING = true;
}
}
async handlePlaceBidSandbox() {
// call to trickger server send mail
await axios({
url: this.ACTION_URL(),
method: "POST",
data: {
id: this.id,
record_url: `${process.env.BASE_URL}admin/bids/record/${this.name_record}`,
},
});
await this.close();
}
async handleReturnProductPage() {
await this.page_context.goto(this.url);
await delay(1000);
}
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;
}
}
update = async () => {
if (!this.page_context) return;
const page = this.page_context;
try {
const close_time = await this.getCloseTime();
// Chờ phần tử xuất hiện trước khi lấy giá trị
await page
.waitForSelector("#priceValue", { timeout: 5000 })
.catch(() => null);
const price_value = await page
.$eval("#priceValue", (el) => el.value)
.catch(() => null);
await page.waitForSelector("#lotId", { timeout: 5000 }).catch(() => null);
const lot_id = await page
.$eval("#lotId", (el) => el.value)
.catch(() => null);
await page
.waitForSelector("#placebid-sticky > div:nth-child(2) > div > h3", {
timeout: 5000,
})
.catch(() => null);
const name = await page
.$eval(".dls-heading-3.lotPageTitle", (el) => el.innerText)
.catch(() => null);
await page
.waitForSelector(
"#biddableLot > form > div > div:nth-child(1) > div:nth-child(1) > div:nth-child(2) > div > span > span",
{ timeout: 5000 }
)
.catch(() => null);
const current_price = await page
.$eval(
"#biddableLot > form > div > div:nth-child(1) > div:nth-child(1) > div:nth-child(2) > div > span > span",
(el) => el.innerText
)
.catch(() => null);
console.log(
`📌 [${this.id}] Product Info: Lot ID: ${lot_id}, Name: ${name}, Current Price: ${current_price}, Reserve price: ${price_value}`
);
const data = removeFalsyValues(
{
lot_id,
reserve_price: price_value,
close_time: close_time ? String(close_time) : null,
name,
current_price: current_price ? extractNumber(current_price) : null,
},
["close_time"]
);
this.handleUpdateBid(data);
return { price_value, lot_id, name, current_price };
} catch (error) {
console.error(`🚨 Error updating product info: ${error.message}`);
return null;
}
};
getCurrentData = async () => {
if (!this.page_context) return null;
try {
// Lấy thời gian đóng
const close_time = await this.getCloseTime();
// Giá trị reserve price
await this.page_context
.waitForSelector("#priceValue", { timeout: 5000 })
.catch(() => null);
const price_value = await this.page_context
.$eval("#priceValue", (el) => el.value)
.catch(() => null);
// Lot ID
await this.page_context
.waitForSelector("#lotId", { timeout: 5000 })
.catch(() => null);
const lot_id = await this.page_context
.$eval("#lotId", (el) => el.value)
.catch(() => null);
// Tên sản phẩm
await this.page_context
.waitForSelector(".dls-heading-3.lotPageTitle", { timeout: 5000 })
.catch(() => null);
const name = await this.page_context
.$eval(".dls-heading-3.lotPageTitle", (el) => el.innerText)
.catch(() => null);
// Giá hiện tại
await this.page_context
.waitForSelector(
"#biddableLot > form > div > div:nth-child(1) > div:nth-child(1) > div:nth-child(2) > div > span > span",
{ timeout: 5000 }
)
.catch(() => null);
const current_price_raw = await this.page_context
.$eval(
"#biddableLot > form > div > div:nth-child(1) > div:nth-child(1) > div:nth-child(2) > div > span > span",
(el) => el.innerText
)
.catch(() => null);
const current_price = current_price_raw
? extractNumber(current_price_raw)
: null;
return removeFalsyValues(
{
lot_id,
reserve_price: Number(price_value) || 0,
close_time: close_time ? String(close_time) : null,
name,
current_price,
},
["close_time"]
);
} catch (error) {
console.error(`🚨 Error fetching current product data: ${error.message}`);
return null;
}
};
async handlePlaceBid() {
if (!this.page_context) {
console.log(
`⚠️ [${this.id}] No page context found, aborting bid process.`
);
return;
}
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;
// Tắt clearLazyTab vì web này phải navigate để bid
global.IS_CLEANING = false;
const isCloseProduct = await this.isCloseProduct();
if (isCloseProduct.result) {
console.log(
`⚠️ [${this.id}] Outbid detected, calling outBid function.`
);
await outBid(this.id);
return;
}
const biddedData = this.getBidedData();
const isBided = (biddedData || []).find(
(item) => item.model === this.model && item.max_price === this.max_price
);
if (isBided) {
if (this.histories.length <= 0 && isTimeReached(this.start_bid_time)) {
pushPrice({
bid_id: this.id,
price: this.max_price,
});
}
console.log(`[${this.id}] This item bided. Skipping...`);
global[`IS_PLACE_BID-${this.id}`] = false;
global.IS_CLEANING = true;
return;
}
if (!this.start_bid_time || !this.close_time) {
console.log(
`[${this.id}] Skipping processing: auction has started but not yet closed.`
);
global.IS_CLEANING = true;
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
global.IS_CLEANING = true;
return; // Dừng hàm nếu giá đã vượt qua giới hạn
}
// Kiểm tra thời gian bid
if (this.start_bid_time && !isTimeReached(this.start_bid_time)) {
console.log(
`⏳ [${this.id}] Not yet time to bid. Skipping Product: ${
this.name || "None"
}`
);
global.IS_CLEANING = true;
return;
}
if (this.histories.length > 0) {
console.log(
`[${this.id}] Already biding with price ${this.histories[0].price}`
);
return;
}
const bids = await this.getCurrentHistories();
const bidedData = this.bidedData(bids);
console.log({ bidedData });
if (bidedData && bidedData.Price >= this.max_price + this.plus_price) {
console.log(`[${this.id}] Bidded with ${bidedData.Price}`);
return;
}
console.log(
"-------------------------------------BIDING---------------------------------------"
);
await this.startRecordSandbox();
const result = await this.placeBid();
global.IS_CLEANING = true;
global[`IS_PLACE_BID-${this.id}`] = false;
} catch (error) {
console.log(`🚨 [${this.id}] Error placing bid: ${error.message}`);
} finally {
await this.stopRecordSandbox();
console.log(`🔚 [${this.id}] Resetting bid flag.`);
}
}
async getCurrentHistories() {
const { data } = await axios({
url: `https://www.grays.com/api/LotInfo/GetBiddingHistory?lotId=${this.lot_id}`,
});
const bids = data?.Bids || [];
return bids;
}
bidedData(bids) {
// A.V - Lidcombe NSW
const data = bids.find(
(bid) =>
`${bid?.UserInitials} - ${bid.UserShortAddress}` == "A.V - Lidcombe NSW"
);
return data;
}
getBidedData() {
return global[`BIDED_DATA_${this.web_bid?.origin_url}`];
}
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.message}`
);
}
};
}