diff --git a/auto-bid-admin/src/apis/dashboard.ts b/auto-bid-admin/src/apis/dashboard.ts new file mode 100644 index 0000000..98fab82 --- /dev/null +++ b/auto-bid-admin/src/apis/dashboard.ts @@ -0,0 +1,36 @@ +import { handleError, handleSuccess } from '.'; +import axios from '../lib/axios'; + +const BASE_URL = 'dashboards'; + +export const resetTool = async () => { + try { + const { data } = await axios({ + url: `${BASE_URL}/reset-tool`, + withCredentials: true, + method: 'POST', + }); + + handleSuccess(data); + + return data; + } catch (error) { + handleError(error); + } +}; + +export const shutdownTool = async () => { + try { + const { data } = await axios({ + url: `${BASE_URL}/shutdown-tool`, + withCredentials: true, + method: 'POST', + }); + + handleSuccess(data); + + return data; + } catch (error) { + handleError(error); + } +}; diff --git a/auto-bid-admin/src/lib/table/table.tsx b/auto-bid-admin/src/lib/table/table.tsx index 06a547c..152c3b8 100644 --- a/auto-bid-admin/src/lib/table/table.tsx +++ b/auto-bid-admin/src/lib/table/table.tsx @@ -696,7 +696,7 @@ const Table = >({ {chooseOptions?.renderBody ? ( chooseOptions.renderBody(chooses, row, (e) => handleChooseSingle(e, row)) ) : ( - + e.stopPropagation()} style={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}> ([]); + const { setConfirm } = useConfirmStore(); + + const [loading, setLoading] = useState(false); const RETRY_CONNECT = useRef(2); @@ -69,12 +75,47 @@ export default function DashBoard() { }; }, []); + const handleResetTool = () => { + setConfirm({ + handleOk: async () => { + setLoading(true); + await resetTool(); + setLoading(false); + }, + title: 'Confirm tool reset', + message: 'Are you sure you want to reset this tool? All current processes will be stopped and restarted.', + okButton: { value: 'Ok', color: 'blue' }, + }); + }; + + const handleShutdownTool = () => { + setConfirm({ + handleOk: async () => { + setLoading(true); + await shutdownTool(); + setLoading(false); + }, + title: 'Confirm tool shutdown', + message: 'Are you sure you want to shut down this tool? All running processes will be stopped and the tool will go offline.', + okButton: { value: 'Ok', color: 'blue' }, + }); + }; + return ( - - Admin Dashboard - - + + + Admin Dashboard + + + + + + {workingData.length > 0 && workingData.map((item, index) => )} @@ -84,6 +125,8 @@ export default function DashBoard() { )} + + ); } diff --git a/auto-bid-server/bot-data/metadata.json b/auto-bid-server/bot-data/metadata.json index 3dedfb9..3283895 100644 --- a/auto-bid-server/bot-data/metadata.json +++ b/auto-bid-server/bot-data/metadata.json @@ -1 +1 @@ -{"createdAt":1744420823691} \ No newline at end of file +{"createdAt":1744861741554} \ No newline at end of file diff --git a/auto-bid-server/src/modules/bids/bids.module.ts b/auto-bid-server/src/modules/bids/bids.module.ts index 206a708..c997473 100644 --- a/auto-bid-server/src/modules/bids/bids.module.ts +++ b/auto-bid-server/src/modules/bids/bids.module.ts @@ -25,6 +25,8 @@ import { ImapService } from './services/mail/imap.service'; import { OutBidLogsService } from './services/out-bid-logs.service'; import { SendMessageHistoriesService } from './services/send-message-histories.service'; import { WebBidsService } from './services/web-bids.service'; +import { DashboardService } from './services/dashboard.service'; +import { AdminDashboardController } from './controllers/admin/admin-dashboard.controller'; @Module({ imports: [ @@ -48,6 +50,7 @@ import { WebBidsService } from './services/web-bids.service'; AdminOutBidLogsController, AdminWebBidsController, AdminSendMessageHistoriesController, + AdminDashboardController, ], providers: [ BidsService, @@ -60,6 +63,7 @@ import { WebBidsService } from './services/web-bids.service'; GraysApi, SendMessageHistoriesService, ImapService, + DashboardService, ], exports: [BotTelegramApi, SendMessageHistoriesService, BidsService], }) diff --git a/auto-bid-server/src/modules/bids/controllers/admin/admin-dashboard.controller.ts b/auto-bid-server/src/modules/bids/controllers/admin/admin-dashboard.controller.ts new file mode 100644 index 0000000..9816269 --- /dev/null +++ b/auto-bid-server/src/modules/bids/controllers/admin/admin-dashboard.controller.ts @@ -0,0 +1,17 @@ +import { Controller, Post } from '@nestjs/common'; +import { DashboardService } from '../../services/dashboard.service'; + +@Controller('admin/dashboards') +export class AdminDashboardController { + constructor(private readonly dashboardService: DashboardService) {} + + @Post('reset-tool') + async resetTool() { + return await this.dashboardService.resetTool(); + } + + @Post('shutdown-tool') + async shutdownTool() { + return await this.dashboardService.shutdownTool(); + } +} diff --git a/auto-bid-server/src/modules/bids/services/bids.service.ts b/auto-bid-server/src/modules/bids/services/bids.service.ts index e010730..a289f37 100644 --- a/auto-bid-server/src/modules/bids/services/bids.service.ts +++ b/auto-bid-server/src/modules/bids/services/bids.service.ts @@ -31,6 +31,7 @@ import { Constant } from '../utils/constant'; import { WebBidsService } from './web-bids.service'; import { NotificationService } from '@/modules/notification/notification.service'; import { Event } from '../utils/events'; +import _ from 'lodash'; @Injectable() export class BidsService { @@ -177,7 +178,7 @@ export class BidsService { }, }); - if (lastHistory && lastHistory.price + bid.plus_price >= bid.max_price) { + if (lastHistory && lastHistory.price + bid.plus_price > bid.max_price) { throw new BadRequestException( AppResponse.toResponse(false, { message: 'Price is out of Max Price' }), ); @@ -205,7 +206,7 @@ export class BidsService { // Tìm kiếm phiên đấu giá trong database theo id const bid = await this.bidsRepo.findOne({ where: { id }, - relations: { histories: true }, + relations: { histories: true, web_bid: true }, order: { histories: { price: 'DESC', @@ -245,7 +246,12 @@ export class BidsService { } } else { // Nếu phiên đấu giá vẫn đang diễn ra và giá hiện tại vượt quá giới hạn đặt của người dùng - if (data.current_price > bid.max_price + bid.plus_price) { + + if ( + data.current_price > bid.max_price + bid.plus_price || + (!bid.histories.length && + data.reserve_price > bid.max_price + bid.plus_price) + ) { bid.status = 'out-bid'; // Gán trạng thái là đã bị outbid } } @@ -267,6 +273,7 @@ export class BidsService { const result = await this.bidsRepo.save({ ...bid, ...data, + current_price: Math.max(data.current_price, bid.current_price), updated_at: new Date(), // Cập nhật timestamp }); diff --git a/auto-bid-server/src/modules/bids/services/dashboard.service.ts b/auto-bid-server/src/modules/bids/services/dashboard.service.ts new file mode 100644 index 0000000..7485196 --- /dev/null +++ b/auto-bid-server/src/modules/bids/services/dashboard.service.ts @@ -0,0 +1,97 @@ +import AppResponse from '@/response/app-response'; +import { Injectable } from '@nestjs/common'; +import { exec } from 'child_process'; +@Injectable() +export class DashboardService { + constructor() {} + + private readonly tool_name = 'auto-bid-tool'; + + async resetToolByName(toolName: string): Promise { + return new Promise((resolve, reject) => { + // Lấy danh sách process đang chạy + exec('pm2 jlist', (error, stdout, stderr) => { + if (error) { + return reject(`Error get list process: ${stderr}`); + } + + try { + const processList = JSON.parse(stdout); + const targetProcess = processList.find( + (proc: any) => proc.name === toolName, + ); + + if (!targetProcess) { + return reject(`Not found process for name "${toolName}"`); + } + + const pmId = targetProcess.pm_id; + + // Restart process theo pm_id + exec(`pm2 restart ${pmId}`, (err, out, errOut) => { + if (err) { + return reject(`Error restart process: ${errOut}`); + } + resolve(`Restarted process "${toolName}" with pm_id ${pmId}`); + }); + } catch (parseErr) { + reject(`Error parse JSON output: ${parseErr}`); + } + }); + }); + } + + async shutdownToolByName(toolName: string): Promise { + return new Promise((resolve, reject) => { + // Lấy danh sách process đang chạy + exec('pm2 jlist', (error, stdout, stderr) => { + if (error) { + return reject(`Error get list process: ${stderr}`); + } + + try { + const processList = JSON.parse(stdout); + const targetProcess = processList.find( + (proc: any) => proc.name === toolName, + ); + + if (!targetProcess) { + return reject(`Not found process for name "${toolName}"`); + } + + const pmId = targetProcess.pm_id; + + // Restart process theo pm_id + exec(`pm2 stop ${pmId}`, (err, out, errOut) => { + if (err) { + return reject(`Error restart process: ${errOut}`); + } + resolve(`Restarted process "${toolName}" with pm_id ${pmId}`); + }); + } catch (parseErr) { + reject(`Error parse JSON output: ${parseErr}`); + } + }); + }); + } + + async resetTool() { + try { + await this.resetToolByName(this.tool_name); + + return AppResponse.toResponse(true); + } catch (error) { + return AppResponse.toResponse(false); + } + } + + async shutdownTool() { + try { + await this.shutdownToolByName(this.tool_name); + + return AppResponse.toResponse(true); + } catch (error) { + return AppResponse.toResponse(false); + } + } +} diff --git a/auto-bid-tool/ecosystem.config.cjs b/auto-bid-tool/ecosystem.config.cjs new file mode 100644 index 0000000..9cc2030 --- /dev/null +++ b/auto-bid-tool/ecosystem.config.cjs @@ -0,0 +1,16 @@ +module.exports = { + apps: [ + { + name: 'auto-bid-tool', + script: './index.js', + instances: 1, + exec_mode: 'fork', + watch: false, + log_date_format: 'YYYY-MM-DD HH:mm:ss', + output: './logs/out.log', + error: './logs/error.log', + merge_logs: true, + max_memory_restart: '12G', + }, + ], +}; diff --git a/auto-bid-tool/index.js b/auto-bid-tool/index.js index 8d2cfee..7fd99bf 100644 --- a/auto-bid-tool/index.js +++ b/auto-bid-tool/index.js @@ -161,7 +161,21 @@ const clearLazyTab = async () => { if (!activeUrls.includes(pageUrl)) { if (!page.isClosed() && browser.isConnected()) { try { - await page.close(); + const bidData = MANAGER_BIDS.filter((item) => item.page_context) + .map((i) => ({ + current_url: i.page_context.url(), + data: i, + })) + .find((j) => j.current_url === pageUrl); + + console.log(bidData); + + if (bidData && bidData.data) { + await safeClosePage(bidData.data); + } else { + await page.close(); + } + console.log(`🛑 Closing unused tab: ${pageUrl}`); } catch (err) { console.warn(`⚠️ Error closing tab ${pageUrl}:, err.message`); diff --git a/auto-bid-tool/models/langtons.com.au/langtons-product-bid.js b/auto-bid-tool/models/langtons.com.au/langtons-product-bid.js index 17d2789..d3f1307 100644 --- a/auto-bid-tool/models/langtons.com.au/langtons-product-bid.js +++ b/auto-bid-tool/models/langtons.com.au/langtons-product-bid.js @@ -123,8 +123,10 @@ export class LangtonsProductBid extends ProductBid { { model: result?.pid || null, lot_id: result?.lotId || null, - reserve_price: result.lotData?.minimumBid || null, - current_price: result.lotData?.currentMaxBid || null, + reserve_price: 21, //test + // reserve_price: result.lotData?.minimumBid || null, + // current_price: result.lotData?.currentMaxBid || null, + current_price: 20, // test // close_time: close_time && !this.close_time ? String(close_time) : null, close_time: close_time ? String(close_time) : null, name, @@ -212,7 +214,7 @@ export class LangtonsProductBid extends ProductBid { 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) { + 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; } @@ -370,7 +372,7 @@ export class LangtonsProductBid extends ProductBid { } if (lotData.myBid && this.max_price && this.max_price != lotData.myBid) { - // this.handlePlaceBid(); + this.handlePlaceBid(); } } catch (error) { console.error(`🚨 [${this.id}] Error parsing API response:`, error); @@ -399,7 +401,7 @@ export class LangtonsProductBid extends ProductBid { await this.gotoLink(); } - // await this.handlePlaceBid(); + await this.handlePlaceBid(); } catch (error) { console.error(`🚨 [${this.id}] Error navigating the page: ${error}`); } diff --git a/auto-bid-tool/models/lawsons.com.au/lawsons-api-bid.js b/auto-bid-tool/models/lawsons.com.au/lawsons-api-bid.js index 9c491dd..fed52c6 100644 --- a/auto-bid-tool/models/lawsons.com.au/lawsons-api-bid.js +++ b/auto-bid-tool/models/lawsons.com.au/lawsons-api-bid.js @@ -14,8 +14,8 @@ export class LawsonsApiBid extends ApiBid { // Tạo timeout để reject sau 1 phút nếu không có phản hồi const timeout = setTimeout(() => { global.socket.off(`verify-code.${this.origin_url}`); // Xóa listener tránh rò rỉ bộ nhớ - rej(new Error(`[${this.id}] Timeout: No verification code received within 1 minute.`)); - }, 60 * 1000); // 60 giây + rej(new Error(`[${this.id}] Timeout: No verification code received within 2 minute.`)); + }, 120 * 1000); // 60 giây global.socket.on(`verify-code.${this.origin_url}`, async (data) => { console.log(`📢 [${this.id}] VERIFY CODE:`, data); @@ -246,8 +246,6 @@ export class LawsonsApiBid extends ApiBid { console.log(`🔄 [${this.id}] Reloading page...`); await this.page_context.reload({ waitUntil: 'networkidle2' }); console.log(`✅ [${this.id}] Page reloaded successfully.`); - - // this.handleUpdateWonItem(); } else { console.log(`❌ [${this.id}] Page context is closed. Stopping reload.`); clearInterval(this.reloadInterval); @@ -255,6 +253,6 @@ export class LawsonsApiBid extends ApiBid { } catch (error) { console.error(`🚨 [${this.id}] Error reloading page:`, error.message); } - }, 60000); + }, 60000); // 1p reload }; } diff --git a/auto-bid-tool/models/lawsons.com.au/lawsons-product-bid.js b/auto-bid-tool/models/lawsons.com.au/lawsons-product-bid.js index 9984440..bda14cc 100644 --- a/auto-bid-tool/models/lawsons.com.au/lawsons-product-bid.js +++ b/auto-bid-tool/models/lawsons.com.au/lawsons-product-bid.js @@ -1,6 +1,8 @@ -import { updateBid } from '../../system/apis/bid.js'; +import _ from 'lodash'; +import { pushPrice, updateBid } from '../../system/apis/bid.js'; +import { sendMessage } from '../../system/apis/notification.js'; import configs from '../../system/config.js'; -import { extractPriceNumber, removeFalsyValues } from '../../system/utils.js'; +import { delay, extractPriceNumber, isTimeReached, removeFalsyValues } from '../../system/utils.js'; import { ProductBid } from '../product-bid.js'; export class LawsonsProductBid extends ProductBid { @@ -20,10 +22,9 @@ export class LawsonsProductBid extends ProductBid { async getReversePrice() { 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('.select-dropdown-value.text-truncate', { timeout: 2000 }); + await this.page_context.waitForSelector('.select-dropdown-value.text-truncate', { timeout: 4000 }); const price = await this.page_context.evaluate(() => { const el = document.querySelector('.select-dropdown-value.text-truncate'); return el ? el.innerText : null; @@ -31,8 +32,7 @@ export class LawsonsProductBid extends ProductBid { return price ? extractPriceNumber(price) : null; } catch (error) { - console.log(error); - // Nếu có lỗi xảy ra trong quá trình lấy thời gian, trả về null + console.log(error.message); return null; } } @@ -41,6 +41,10 @@ export class LawsonsProductBid extends ProductBid { try { if (!this.page_context) return; + // if (this.updated_at) { + // await this.page_context.reload({ waitUntil: 'networkidle0' }); + // } + const result = await this.waitApiInfo(); const reservePrice = await this.getReversePrice(); @@ -55,8 +59,10 @@ export class LawsonsProductBid extends ProductBid { reserve_price: reservePrice, current_price: result?.currentBidAmount || null, close_time: new Date(result.endTime).toUTCString() || null, + // close_time: this.close_time ? null : new Date(Date.now() + 5 * 60 * 1000).toUTCString(), //test name: result?.itemView?.title || null, }, + // [], ['close_time'], ); @@ -84,6 +90,153 @@ export class LawsonsProductBid extends ProductBid { }, url); }; + submitBid() { + return new Promise(async (resolve, reject) => { + if (!this.page_context || !this.model) { + console.log(`[${this.id}] Page context or model is missing.`); + reject(null); + 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({ + bidAmount, + lotRef, + v2: true, + }), + }); + + if (!response.ok) { + throw new Error(`HTTP ${response.status}`); + } + + return await response.json(); + }, + this.max_price, + this.model, + configs.WEB_CONFIGS.LAWSONS.API_CHECKOUT, + ); + + console.log('🧾 API Bid Result:', { + bid_amount: this.max_price, + result, + }); + + if (!result?.data?.orderBidResponse?.success) reject(null); + + resolve(result); + } catch (err) { + console.log(`[${this.id}] Failed to submit bid: ${err.message}`); + reject(null); + } + }); + } + + 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.waitApiInfo(); + + // Lấy giá reserve price để kiểm tra + const reservePrice = await this.getReversePrice(); + + // Kiểm tra nếu có lý do nào khiến không thể tiếp tục đấu giá + const shouldStop = + !response || + response?.currentBidAmount > this.max_price + this.plus_price || + response.isOutBid != true || + !reservePrice || + reservePrice > this.max_price + this.plus_price; + + if (shouldStop) { + console.log(`⚠️ [${this.id}] Stop bidding:`, { reservePrice, 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}] ================`); + + await delay(20000); + + // 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ả + const result = await this.submitBid(); + + // Nếu không có kết quả (lỗi khi gửi bid) thì dừng lại + if (!result) return; + + console.log({ result }); + + // Gửi thông báo đã đấu giá thành công + sendMessage(this); + + await this.page_context.reload({ waitUntil: 'networkidle0' }); + + 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(`🚨 [${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 waitApiInfo() { if (!this.page_context) { console.error(`❌ [${this.id}] Error: page_context is undefined.`); @@ -98,6 +251,104 @@ export class LawsonsProductBid extends ProductBid { return { ...info, ...detailData }; } + async trackingOutbid() { + if (!this.page_context) return; + + try { + const onResponse = async (response) => { + const url = response?.request()?.url(); + if (!url || !url.includes(configs.WEB_CONFIGS.LAWSONS.API_DETAIL_INFO(this.model))) { + return; + } + + try { + const result = await response.json(); + + if (!result) return; + + console.log(`📈 [${this.id}] Bid data: `, result); + + const { maxBidAmount, currentBidAmount, isOutBid } = result; + + console.log(`📊 [${this.id}] API Info - maxBidAmount: ${maxBidAmount}, currentBidAmount: ${currentBidAmount}, isOutBid: ${isOutBid}`); + + // Lấy giá reverse (giá thấp nhất cần để thắng đấu giá) + const reversePrice = await this.getReversePrice(); + 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: 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 && isOutBid && currentBidAmount <= this.max_price + this.plus_price && this.max_price != maxBidAmount) { + 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.`); + } + + if (new Date(this.updated_at).getTime() > Date.now() - 120 * 1000) { + await this.page_context.reload({ waitUntil: 'networkidle0' }); + } + } 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); + } + } + + 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; @@ -107,6 +358,8 @@ export class LawsonsProductBid extends ProductBid { 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}`); } diff --git a/auto-bid-tool/system/config.js b/auto-bid-tool/system/config.js index 1c2ba52..68de7de 100644 --- a/auto-bid-tool/system/config.js +++ b/auto-bid-tool/system/config.js @@ -26,6 +26,7 @@ const configs = { API_DETAIL_PRODUCT: (model) => { return `https://www.lawsons.com.au/api/auctions/lot/${model}`; }, + API_CHECKOUT: 'https://www.lawsons.com.au/app/orderBid', }, }, };