fix grays
This commit is contained in:
parent
44c3934daa
commit
fe4d22f361
|
|
@ -26,12 +26,12 @@ const schema = {
|
||||||
.number({ message: "Arrival offset seconds is required" })
|
.number({ message: "Arrival offset seconds is required" })
|
||||||
.refine((val) => val >= 60, {
|
.refine((val) => val >= 60, {
|
||||||
message: "Arrival offset seconds must be at least 60 seconds (1 minute)",
|
message: "Arrival offset seconds must be at least 60 seconds (1 minute)",
|
||||||
}),
|
}).optional(),
|
||||||
early_tracking_seconds: z
|
early_tracking_seconds: z
|
||||||
.number({ message: "Early login seconds is required" })
|
.number({ message: "Early login seconds is required" })
|
||||||
.refine((val) => val >= 600, {
|
.refine((val) => val >= 600, {
|
||||||
message: "Early login seconds must be at least 600 seconds (10 minute)",
|
message: "Early login seconds must be at least 600 seconds (10 minute)",
|
||||||
}),
|
}).optional(),
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function WebBidModal({
|
export default function WebBidModal({
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
{"createdAt":1747011314493}
|
{"createdAt":1747107717780}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { IsNumber, IsString, IsUrl } from 'class-validator';
|
import { IsNumber, IsOptional, IsString, IsUrl, Min } from 'class-validator';
|
||||||
|
|
||||||
export class CreateWebBidDto {
|
export class CreateWebBidDto {
|
||||||
@IsUrl()
|
@IsUrl()
|
||||||
|
|
@ -6,4 +6,14 @@ export class CreateWebBidDto {
|
||||||
|
|
||||||
@IsUrl()
|
@IsUrl()
|
||||||
url: string;
|
url: string;
|
||||||
|
|
||||||
|
@IsNumber()
|
||||||
|
@Min(60)
|
||||||
|
@IsOptional()
|
||||||
|
arrival_offset_seconds: number;
|
||||||
|
|
||||||
|
@IsNumber()
|
||||||
|
@Min(600)
|
||||||
|
@IsOptional()
|
||||||
|
early_tracking_seconds: number;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -184,6 +184,8 @@ const tracking = async () => {
|
||||||
|
|
||||||
// Kiểm tra URL và điều hướng nếu cần
|
// Kiểm tra URL và điều hướng nếu cần
|
||||||
if ((await productTab.page_context.url()) !== productTab.url) {
|
if ((await productTab.page_context.url()) !== productTab.url) {
|
||||||
|
if (global[`IS_PLACE_BID-${productTab.id}`]) return;
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
`🔄 Redirecting to new URL for Product ID: ${productTab.id}`
|
`🔄 Redirecting to new URL for Product ID: ${productTab.id}`
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -98,9 +98,15 @@ export class ApiBid extends Bid {
|
||||||
if (this.snapshot_at) {
|
if (this.snapshot_at) {
|
||||||
const nearestCloseTime = findNearestClosingChild(this);
|
const nearestCloseTime = findNearestClosingChild(this);
|
||||||
|
|
||||||
if (!nearestCloseTime) {
|
if (!nearestCloseTime || this.children.some((item) => !item.close_time)) {
|
||||||
console.log(`❌ [${this.id}] No nearest closing child found.`);
|
console.log(`🔌 [${this.id}] Connecting to puppeteer...`);
|
||||||
return false;
|
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;
|
const { close_time } = nearestCloseTime;
|
||||||
|
|
@ -137,10 +143,13 @@ export class ApiBid extends Bid {
|
||||||
// Nếu chưa có ảnh chụp working => tab not lazy
|
// Nếu chưa có ảnh chụp working => tab not lazy
|
||||||
if (!this.snapshot_at) return false;
|
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);
|
const nearestCloseTime = findNearestClosingChild(this);
|
||||||
|
|
||||||
// Nếu không có nearest close => tab lazy
|
// Nếu không có nearest close => tab not lazy
|
||||||
if (!nearestCloseTime) return true;
|
if (!nearestCloseTime) return false;
|
||||||
|
|
||||||
const { close_time } = nearestCloseTime;
|
const { close_time } = nearestCloseTime;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -125,37 +125,37 @@ export class GrayApiBid extends ApiBid {
|
||||||
(bid) => !this.children_processing.some((item) => item.model === bid.Sku)
|
(bid) => !this.children_processing.some((item) => item.model === bid.Sku)
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleChildren = this.children.filter((item) =>
|
// const handleChildren = this.children.filter((item) =>
|
||||||
bidOutLots.some((i) => i.Sku === item.model)
|
// bidOutLots.some((i) => i.Sku === item.model)
|
||||||
);
|
// );
|
||||||
|
|
||||||
console.log({
|
// console.log({
|
||||||
handleChildren,
|
// handleChildren,
|
||||||
children_processing: this.children_processing,
|
// children_processing: this.children_processing,
|
||||||
data,
|
// data,
|
||||||
bidOutLots,
|
// bidOutLots,
|
||||||
});
|
// });
|
||||||
|
|
||||||
for (const product_tab of handleChildren) {
|
// for (const product_tab of handleChildren) {
|
||||||
if (!isTimeReached(product_tab.start_bid_time)) {
|
// if (!isTimeReached(product_tab.start_bid_time)) {
|
||||||
console.log(
|
// console.log(
|
||||||
`❌ [${this.id}] It's not time yet ID: ${product_tab.id} continue waiting...`
|
// `❌ [${this.id}] It's not time yet ID: ${product_tab.id} continue waiting...`
|
||||||
);
|
// );
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
|
|
||||||
this.children_processing.push(product_tab);
|
// this.children_processing.push(product_tab);
|
||||||
|
|
||||||
if (!product_tab.page_context) {
|
// if (!product_tab.page_context) {
|
||||||
await product_tab.puppeteer_connect();
|
// await product_tab.puppeteer_connect();
|
||||||
}
|
// }
|
||||||
|
|
||||||
await product_tab.action();
|
// await product_tab.action();
|
||||||
|
|
||||||
this.children_processing = this.children_processing.filter(
|
// this.children_processing = this.children_processing.filter(
|
||||||
(item) => item.id !== product_tab.id
|
// (item) => item.id !== product_tab.id
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
};
|
};
|
||||||
|
|
||||||
isLogin = async () => {
|
isLogin = async () => {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,322 @@
|
||||||
|
import {
|
||||||
|
outBid,
|
||||||
|
pushPrice,
|
||||||
|
updateBid,
|
||||||
|
updateStatusByPrice,
|
||||||
|
} from "../../system/apis/bid.js";
|
||||||
|
import CONSTANTS from "../../system/constants.js";
|
||||||
|
import {
|
||||||
|
delay,
|
||||||
|
extractNumber,
|
||||||
|
isNumber,
|
||||||
|
isTimeReached,
|
||||||
|
removeFalsyValues,
|
||||||
|
safeClosePage,
|
||||||
|
takeSnapshot,
|
||||||
|
} from "../../system/utils.js";
|
||||||
|
import { ProductBid } from "../product-bid.js";
|
||||||
|
|
||||||
|
export class GraysProductBidBackup extends ProductBid {
|
||||||
|
constructor({ ...prev }) {
|
||||||
|
super(prev);
|
||||||
|
}
|
||||||
|
|
||||||
|
async validate({ page, price_value }) {
|
||||||
|
if (!this.start_bid_time || !isTimeReached(this.start_bid_time)) {
|
||||||
|
console.log(`❌ [${this.id}] It's not time yet`);
|
||||||
|
return { result: false, bid_price: 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isNumber(price_value)) {
|
||||||
|
console.log(`❌ [${this.id}] Can't get PRICE_VALUE`);
|
||||||
|
await takeSnapshot(page, this, "price-value-null");
|
||||||
|
|
||||||
|
return { result: false, bid_price: 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
const bid_price = this.plus_price + Number(price_value);
|
||||||
|
|
||||||
|
if (bid_price > this.max_price) {
|
||||||
|
console.log(
|
||||||
|
`❌ ${this.id} PRICE BID is more than MAX_VALUE => STOP BID THIS PRODUCT`
|
||||||
|
);
|
||||||
|
await takeSnapshot(page, this, "price-bid-more-than");
|
||||||
|
|
||||||
|
await outBid(this.id);
|
||||||
|
|
||||||
|
return { result: false, bid_price: 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await pushPrice({
|
||||||
|
bid_id: this.id,
|
||||||
|
price: bid_price,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.status) {
|
||||||
|
return { result: false, bid_price: 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
this.histories = response.data;
|
||||||
|
|
||||||
|
// RESET first bid
|
||||||
|
if (this.histories.length > 0 && this.first_bid) {
|
||||||
|
this.first_bid = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { result: true, bid_price };
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
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 handleWritePrice(page, bid_price) {
|
||||||
|
await page.type("#price", String(bid_price));
|
||||||
|
await delay(500);
|
||||||
|
}
|
||||||
|
|
||||||
|
async placeBid(page) {
|
||||||
|
try {
|
||||||
|
await page.click("#bid-type-standard");
|
||||||
|
await delay(500);
|
||||||
|
|
||||||
|
await page.click("#btnSubmit");
|
||||||
|
await delay(1000);
|
||||||
|
|
||||||
|
await page.waitForSelector("button", { timeout: 5000 });
|
||||||
|
|
||||||
|
await delay(500);
|
||||||
|
|
||||||
|
await page.click("button");
|
||||||
|
|
||||||
|
await page.waitForNavigation({ timeout: 5000 });
|
||||||
|
|
||||||
|
await takeSnapshot(
|
||||||
|
page,
|
||||||
|
this,
|
||||||
|
"bid-success",
|
||||||
|
CONSTANTS.TYPE_IMAGE.SUCCESS
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.log(`❌ [${this.id}] Timeout to loading`);
|
||||||
|
await takeSnapshot(page, this, "timeout to loading");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleReturnProductPage(page) {
|
||||||
|
await page.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;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
action = async () => {
|
||||||
|
try {
|
||||||
|
const page = this.page_context;
|
||||||
|
|
||||||
|
await this.gotoLink();
|
||||||
|
console.log(`🌍 [${this.id}] Navigated to link.`);
|
||||||
|
|
||||||
|
await delay(1000);
|
||||||
|
|
||||||
|
const { close_time, ...isCloseProduct } = await this.isCloseProduct();
|
||||||
|
if (isCloseProduct.result) {
|
||||||
|
console.log(
|
||||||
|
`❌ [${this.id}] The product is closed, cannot place a bid.`
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await delay(500);
|
||||||
|
|
||||||
|
const { price_value } = await this.update();
|
||||||
|
if (!price_value) return;
|
||||||
|
|
||||||
|
const { result, bid_price } = await this.validate({ page, price_value });
|
||||||
|
if (!result) {
|
||||||
|
console.log(
|
||||||
|
`❌ [${this.id}] Validation failed. Unable to proceed with bidding.`
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bidHistoriesItem = _.maxBy(this.histories, "price");
|
||||||
|
if (bidHistoriesItem && bidHistoriesItem.price === this.current_price) {
|
||||||
|
console.log(
|
||||||
|
`🔄 [${this.id}] You have already bid on this item! (Bid Price: ${bidHistoriesItem.price})`
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (price_value != bid_price) {
|
||||||
|
console.log(
|
||||||
|
`✍️ [${this.id}] Updating bid price from ${price_value} → ${bid_price}`
|
||||||
|
);
|
||||||
|
await this.handleWritePrice(page, bid_price);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`🚀 [${this.id}] Placing the bid...`);
|
||||||
|
const resultPlaceBid = await this.placeBid(page);
|
||||||
|
if (!resultPlaceBid) {
|
||||||
|
console.log(`❌ [${this.id}] Error occurred while placing the bid.`);
|
||||||
|
await takeSnapshot(page, this, "place-bid-action");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`✅ [${this.id}] Bid placed successfully! 🏆 Bid Price: ${bid_price}, Closing Time: ${close_time}`
|
||||||
|
);
|
||||||
|
await this.handleReturnProductPage(page);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(
|
||||||
|
`🚨 [${this.id}] Error navigating the page: ${error.message}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -21,51 +21,6 @@ export class GraysProductBid extends ProductBid {
|
||||||
super(prev);
|
super(prev);
|
||||||
}
|
}
|
||||||
|
|
||||||
async validate({ page, price_value }) {
|
|
||||||
if (!this.start_bid_time || !isTimeReached(this.start_bid_time)) {
|
|
||||||
console.log(`❌ [${this.id}] It's not time yet`);
|
|
||||||
return { result: false, bid_price: 0 };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isNumber(price_value)) {
|
|
||||||
console.log(`❌ [${this.id}] Can't get PRICE_VALUE`);
|
|
||||||
await takeSnapshot(page, this, "price-value-null");
|
|
||||||
|
|
||||||
return { result: false, bid_price: 0 };
|
|
||||||
}
|
|
||||||
|
|
||||||
const bid_price = this.plus_price + Number(price_value);
|
|
||||||
|
|
||||||
if (bid_price > this.max_price) {
|
|
||||||
console.log(
|
|
||||||
`❌ ${this.id} PRICE BID is more than MAX_VALUE => STOP BID THIS PRODUCT`
|
|
||||||
);
|
|
||||||
await takeSnapshot(page, this, "price-bid-more-than");
|
|
||||||
|
|
||||||
await outBid(this.id);
|
|
||||||
|
|
||||||
return { result: false, bid_price: 0 };
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await pushPrice({
|
|
||||||
bid_id: this.id,
|
|
||||||
price: bid_price,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.status) {
|
|
||||||
return { result: false, bid_price: 0 };
|
|
||||||
}
|
|
||||||
|
|
||||||
this.histories = response.data;
|
|
||||||
|
|
||||||
// RESET first bid
|
|
||||||
if (this.histories.length > 0 && this.first_bid) {
|
|
||||||
this.first_bid = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return { result: true, bid_price };
|
|
||||||
}
|
|
||||||
|
|
||||||
getCloseTime = async () => {
|
getCloseTime = async () => {
|
||||||
try {
|
try {
|
||||||
if (!this.page_context) return null;
|
if (!this.page_context) return null;
|
||||||
|
|
@ -127,43 +82,61 @@ export class GraysProductBid extends ProductBid {
|
||||||
return { result: false, close_time };
|
return { result: false, close_time };
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleWritePrice(page, bid_price) {
|
async placeBid() {
|
||||||
await page.type("#price", String(bid_price));
|
|
||||||
await delay(500);
|
|
||||||
}
|
|
||||||
|
|
||||||
async placeBid(page) {
|
|
||||||
try {
|
try {
|
||||||
await page.click("#bid-type-standard");
|
await this.page_context.evaluate(() => {
|
||||||
await delay(500);
|
document.querySelector("#price").value = "";
|
||||||
|
});
|
||||||
|
|
||||||
await page.click("#btnSubmit");
|
await this.page_context.type("#price", String(this.max_price));
|
||||||
|
|
||||||
|
await delay(5000);
|
||||||
|
|
||||||
|
const currentValue = await this.page_context.$eval(
|
||||||
|
"#price",
|
||||||
|
(el) => el.value
|
||||||
|
);
|
||||||
|
|
||||||
|
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");
|
||||||
await delay(1000);
|
await delay(1000);
|
||||||
|
|
||||||
await page.waitForSelector("button", { timeout: 5000 });
|
await this.page_context.waitForSelector("button", { timeout: 5000 });
|
||||||
|
|
||||||
await delay(500);
|
await delay(500);
|
||||||
|
|
||||||
await page.click("button");
|
await this.page_context.click("button");
|
||||||
|
|
||||||
await page.waitForNavigation({ timeout: 5000 });
|
await this.page_context.waitForNavigation({ timeout: 5000 });
|
||||||
|
|
||||||
await takeSnapshot(
|
await this.page_context.waitForFunction(
|
||||||
page,
|
() => document.body.innerText.includes("Successfully"),
|
||||||
this,
|
{ timeout: 5000 } // hoặc lâu hơn nếu cần
|
||||||
"bid-success",
|
|
||||||
CONSTANTS.TYPE_IMAGE.SUCCESS
|
|
||||||
);
|
);
|
||||||
|
console.log("✅ Found 'Successfully'");
|
||||||
|
|
||||||
|
await pushPrice({
|
||||||
|
bid_id: this.id,
|
||||||
|
price: this.max_price,
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.handleReturnProductPage();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(`❌ [${this.id}] Timeout to loading`);
|
console.log(`❌ [${this.id}] Error in placeBid: ${error.message}`);
|
||||||
await takeSnapshot(page, this, "timeout to loading");
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleReturnProductPage(page) {
|
async handleReturnProductPage() {
|
||||||
await page.goto(this.url);
|
await this.page_context.goto(this.url);
|
||||||
await delay(1000);
|
await delay(1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -256,63 +229,146 @@ export class GraysProductBid extends ProductBid {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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"
|
||||||
|
}`
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.histories.length > 0) {
|
||||||
|
console.log(
|
||||||
|
`[${this.id}] Already biding with price ${this.histories[0].price}`
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
console.log(`🔚 [${this.id}] Resetting bid flag.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
action = async () => {
|
action = async () => {
|
||||||
try {
|
try {
|
||||||
const page = this.page_context;
|
const page = this.page_context;
|
||||||
|
|
||||||
await this.gotoLink();
|
// 📌 Kiểm tra nếu trang chưa tải đúng URL thì điều hướng đến URL mục tiêu
|
||||||
console.log(`🌍 [${this.id}] Navigated to link.`);
|
if (!page.url() || !page.url().includes(this.url)) {
|
||||||
|
console.log(`🔄 [${this.id}] Navigating to target URL: ${this.url}`);
|
||||||
await delay(1000);
|
await this.gotoLink();
|
||||||
|
|
||||||
const { close_time, ...isCloseProduct } = await this.isCloseProduct();
|
|
||||||
if (isCloseProduct.result) {
|
|
||||||
console.log(
|
|
||||||
`❌ [${this.id}] The product is closed, cannot place a bid.`
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await delay(500);
|
await this.handlePlaceBid();
|
||||||
|
|
||||||
const { price_value } = await this.update();
|
|
||||||
if (!price_value) return;
|
|
||||||
|
|
||||||
const { result, bid_price } = await this.validate({ page, price_value });
|
|
||||||
if (!result) {
|
|
||||||
console.log(
|
|
||||||
`❌ [${this.id}] Validation failed. Unable to proceed with bidding.`
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const bidHistoriesItem = _.maxBy(this.histories, "price");
|
|
||||||
if (bidHistoriesItem && bidHistoriesItem.price === this.current_price) {
|
|
||||||
console.log(
|
|
||||||
`🔄 [${this.id}] You have already bid on this item! (Bid Price: ${bidHistoriesItem.price})`
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (price_value != bid_price) {
|
|
||||||
console.log(
|
|
||||||
`✍️ [${this.id}] Updating bid price from ${price_value} → ${bid_price}`
|
|
||||||
);
|
|
||||||
await this.handleWritePrice(page, bid_price);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`🚀 [${this.id}] Placing the bid...`);
|
|
||||||
const resultPlaceBid = await this.placeBid(page);
|
|
||||||
if (!resultPlaceBid) {
|
|
||||||
console.log(`❌ [${this.id}] Error occurred while placing the bid.`);
|
|
||||||
await takeSnapshot(page, this, "place-bid-action");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(
|
|
||||||
`✅ [${this.id}] Bid placed successfully! 🏆 Bid Price: ${bid_price}, Closing Time: ${close_time}`
|
|
||||||
);
|
|
||||||
await this.handleReturnProductPage(page);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(
|
console.error(
|
||||||
`🚨 [${this.id}] Error navigating the page: ${error.message}`
|
`🚨 [${this.id}] Error navigating the page: ${error.message}`
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue