Merge pull request 'Deploy to production' (#42) from staging into production
Reviewed-on: #42
This commit is contained in:
commit
be6fa0cdfc
|
|
@ -118,10 +118,6 @@ export default function WorkingPage({ data, socket }: IWorkingPageProps) {
|
|||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
if (!isIBid(data)) {
|
||||
console.log(data);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box
|
||||
|
|
@ -200,7 +196,7 @@ export default function WorkingPage({ data, socket }: IWorkingPageProps) {
|
|||
</Box>
|
||||
<Box className="flex items-center gap-4">
|
||||
<Button
|
||||
rightSection={<IconImageInPicture size={14}/>}
|
||||
rightSection={<IconImageInPicture size={14} />}
|
||||
size="xs"
|
||||
color="green"
|
||||
onClick={open}
|
||||
|
|
|
|||
|
|
@ -26,12 +26,12 @@ const schema = {
|
|||
.number({ message: "Arrival offset seconds is required" })
|
||||
.refine((val) => val >= 60, {
|
||||
message: "Arrival offset seconds must be at least 60 seconds (1 minute)",
|
||||
}),
|
||||
}).optional(),
|
||||
early_tracking_seconds: z
|
||||
.number({ message: "Early login seconds is required" })
|
||||
.refine((val) => val >= 600, {
|
||||
message: "Early login seconds must be at least 600 seconds (10 minute)",
|
||||
}),
|
||||
}).optional(),
|
||||
};
|
||||
|
||||
export default function WebBidModal({
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
{"createdAt":1747011314493}
|
||||
{"createdAt":1747191706164}
|
||||
|
|
@ -17,6 +17,7 @@
|
|||
"@nestjs/mapped-types": "*",
|
||||
"@nestjs/platform-express": "^10.4.15",
|
||||
"@nestjs/platform-socket.io": "^11.0.11",
|
||||
"@nestjs/schedule": "^6.0.0",
|
||||
"@nestjs/throttler": "^6.4.0",
|
||||
"@nestjs/typeorm": "^11.0.0",
|
||||
"@nestjs/websockets": "^11.0.11",
|
||||
|
|
@ -51,7 +52,7 @@
|
|||
"@types/jest": "^29.5.2",
|
||||
"@types/lodash": "^4.17.16",
|
||||
"@types/multer": "^1.4.12",
|
||||
"@types/node": "^20.3.1",
|
||||
"@types/node": "^20.17.46",
|
||||
"@types/supertest": "^6.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.0.0",
|
||||
"@typescript-eslint/parser": "^6.0.0",
|
||||
|
|
@ -2458,6 +2459,19 @@
|
|||
"rxjs": "^7.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@nestjs/schedule": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@nestjs/schedule/-/schedule-6.0.0.tgz",
|
||||
"integrity": "sha512-aQySMw6tw2nhitELXd3EiRacQRgzUKD9mFcUZVOJ7jPLqIBvXOyvRWLsK9SdurGA+jjziAlMef7iB5ZEFFoQpw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cron": "4.3.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@nestjs/common": "^10.0.0 || ^11.0.0",
|
||||
"@nestjs/core": "^10.0.0 || ^11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@nestjs/schematics": {
|
||||
"version": "10.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@nestjs/schematics/-/schematics-10.2.3.tgz",
|
||||
|
|
@ -3007,6 +3021,12 @@
|
|||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/luxon": {
|
||||
"version": "3.6.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.6.2.tgz",
|
||||
"integrity": "sha512-R/BdP7OxEMc44l2Ex5lSXHoIXTB2JLNa3y2QISIbr58U/YcsffyQrYW//hZSdrfxrjRZj3GcUoxMPGdO8gSYuw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/methods": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/methods/-/methods-1.1.4.tgz",
|
||||
|
|
@ -3032,9 +3052,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "20.17.24",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.24.tgz",
|
||||
"integrity": "sha512-d7fGCyB96w9BnWQrOsJtpyiSaBcAYYr75bnK6ZRjDbql2cGLj/3GsL5OYmLPNq76l7Gf2q4Rv9J2o6h5CrD9sA==",
|
||||
"version": "20.17.46",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.46.tgz",
|
||||
"integrity": "sha512-0PQHLhZPWOxGW4auogW0eOQAuNIlCYvibIpG67ja0TOJ6/sehu+1en7sfceUn+QQtx4Rk3GxbLNwPh0Cav7TWw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~6.19.2"
|
||||
|
|
@ -4875,6 +4895,19 @@
|
|||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/cron": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/cron/-/cron-4.3.0.tgz",
|
||||
"integrity": "sha512-ciiYNLfSlF9MrDqnbMdRWFiA6oizSF7kA1osPP9lRzNu0Uu+AWog1UKy7SkckiDY2irrNjeO6qLyKnXC8oxmrw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/luxon": "~3.6.0",
|
||||
"luxon": "~3.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.x"
|
||||
}
|
||||
},
|
||||
"node_modules/cross-spawn": {
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||
|
|
@ -8335,6 +8368,15 @@
|
|||
"url": "https://github.com/sponsors/wellwelwel"
|
||||
}
|
||||
},
|
||||
"node_modules/luxon": {
|
||||
"version": "3.6.1",
|
||||
"resolved": "https://registry.npmjs.org/luxon/-/luxon-3.6.1.tgz",
|
||||
"integrity": "sha512-tJLxrKJhO2ukZ5z0gyjY1zPh3Rh88Ej9P7jNrZiHMUXHae1yvI2imgOZtL1TO8TW6biMMKfTtAOoEJANgtWBMQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/magic-string": {
|
||||
"version": "0.30.8",
|
||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.8.tgz",
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@
|
|||
"@nestjs/mapped-types": "*",
|
||||
"@nestjs/platform-express": "^10.4.15",
|
||||
"@nestjs/platform-socket.io": "^11.0.11",
|
||||
"@nestjs/schedule": "^6.0.0",
|
||||
"@nestjs/throttler": "^6.4.0",
|
||||
"@nestjs/typeorm": "^11.0.0",
|
||||
"@nestjs/websockets": "^11.0.11",
|
||||
|
|
@ -67,7 +68,7 @@
|
|||
"@types/jest": "^29.5.2",
|
||||
"@types/lodash": "^4.17.16",
|
||||
"@types/multer": "^1.4.12",
|
||||
"@types/node": "^20.3.1",
|
||||
"@types/node": "^20.17.46",
|
||||
"@types/supertest": "^6.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.0.0",
|
||||
"@typescript-eslint/parser": "^6.0.0",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { Module } from '@nestjs/common';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { EventEmitterModule } from '@nestjs/event-emitter';
|
||||
import { ScheduleModule } from '@nestjs/schedule';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
|
|
@ -11,6 +12,7 @@ import { EventEmitterModule } from '@nestjs/event-emitter';
|
|||
wildcard: true,
|
||||
global: true,
|
||||
}),
|
||||
ScheduleModule.forRoot()
|
||||
],
|
||||
})
|
||||
export class AppConfigsModule {}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,9 @@ import { SendMessageHistoriesService } from './services/send-message-histories.s
|
|||
import { WebBidsService } from './services/web-bids.service';
|
||||
import { DashboardService } from './services/dashboard.service';
|
||||
import { AdminDashboardController } from './controllers/admin/admin-dashboard.controller';
|
||||
import { TasksService } from './services/tasks.servise';
|
||||
import { ConfigsService } from './services/configs.service';
|
||||
import { Config } from './entities/configs.entity';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
|
|
@ -36,6 +39,7 @@ import { AdminDashboardController } from './controllers/admin/admin-dashboard.co
|
|||
OutBidLog,
|
||||
WebBid,
|
||||
SendMessageHistory,
|
||||
Config
|
||||
]),
|
||||
// AuthModule,
|
||||
AdminsModule,
|
||||
|
|
@ -64,6 +68,8 @@ import { AdminDashboardController } from './controllers/admin/admin-dashboard.co
|
|||
SendMessageHistoriesService,
|
||||
ImapService,
|
||||
DashboardService,
|
||||
TasksService,
|
||||
ConfigsService
|
||||
],
|
||||
exports: [BotTelegramApi, SendMessageHistoriesService, BidsService],
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { IsNumber, IsString, IsUrl } from 'class-validator';
|
||||
import { IsNumber, IsOptional, IsString, IsUrl, Min } from 'class-validator';
|
||||
|
||||
export class CreateWebBidDto {
|
||||
@IsUrl()
|
||||
|
|
@ -6,4 +6,14 @@ export class CreateWebBidDto {
|
|||
|
||||
@IsUrl()
|
||||
url: string;
|
||||
|
||||
@IsNumber()
|
||||
@Min(60)
|
||||
@IsOptional()
|
||||
arrival_offset_seconds: number;
|
||||
|
||||
@IsNumber()
|
||||
@Min(600)
|
||||
@IsOptional()
|
||||
early_tracking_seconds: number;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
import { Column, Entity, PrimaryGeneratedColumn, Unique } from 'typeorm';
|
||||
import { Timestamp } from './timestamp';
|
||||
|
||||
@Entity('configs')
|
||||
export class Config extends Timestamp {
|
||||
@PrimaryGeneratedColumn('increment')
|
||||
id: number;
|
||||
|
||||
@Column({ unique: true })
|
||||
key_name: string;
|
||||
|
||||
@Column({ nullable: true, default: true })
|
||||
value: string | null;
|
||||
|
||||
@Column()
|
||||
type: 'string' | 'number';
|
||||
}
|
||||
|
|
@ -1,8 +1,9 @@
|
|||
import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from 'typeorm';
|
||||
import { Column, Entity, ManyToOne, PrimaryGeneratedColumn, Unique } from 'typeorm';
|
||||
import { Timestamp } from './timestamp';
|
||||
import { Bid } from './bid.entity';
|
||||
|
||||
@Entity('send_message_histories')
|
||||
@Unique(['max_price', 'type', 'reserve_price'])
|
||||
export class SendMessageHistory extends Timestamp {
|
||||
@PrimaryGeneratedColumn('increment')
|
||||
id: number;
|
||||
|
|
@ -17,4 +18,10 @@ export class SendMessageHistory extends Timestamp {
|
|||
onDelete: 'CASCADE',
|
||||
})
|
||||
bid: Bid;
|
||||
|
||||
@Column({ default: 0, nullable: true })
|
||||
max_price: number;
|
||||
|
||||
@Column({ default: 0, nullable: true })
|
||||
reserve_price: number;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ import { Column } from 'nestjs-paginate/lib/helper';
|
|||
import { join } from 'path';
|
||||
import AppResponse from 'src/response/app-response';
|
||||
import { extractModelId, isTimeReached, subtractMinutes } from 'src/ultils';
|
||||
import { In, Repository } from 'typeorm';
|
||||
import { In, IsNull, Not, Repository } from 'typeorm';
|
||||
import { ClientUpdateBidDto } from '../dto/bid/client-update-bid.dto';
|
||||
import { CreateBidDto } from '../dto/bid/create-bid.dto';
|
||||
import { UpdateBidDto } from '../dto/bid/update-bid.dto';
|
||||
|
|
@ -57,7 +57,7 @@ export class BidsService {
|
|||
lot_id: true,
|
||||
close_time: true,
|
||||
name: [FilterOperator.ILIKE],
|
||||
status: true
|
||||
status: true,
|
||||
};
|
||||
|
||||
query.filter = AppResponse.processFilters(query.filter, filterableColumns);
|
||||
|
|
@ -524,4 +524,29 @@ export class BidsService {
|
|||
|
||||
return AppResponse.toResponse(true);
|
||||
}
|
||||
|
||||
async getNextBid(): Promise<Bid | null> {
|
||||
const all = await this.bidsRepo.find({
|
||||
where: { status: 'biding', close_time: Not(IsNull()) },
|
||||
relations: { web_bid: true },
|
||||
});
|
||||
|
||||
const now = Date.now();
|
||||
|
||||
let nextBid = null;
|
||||
let minDiff = Infinity;
|
||||
|
||||
for (const bid of all) {
|
||||
const time = Date.parse(bid.close_time);
|
||||
if (!isNaN(time) && time >= now) {
|
||||
const diff = time - now;
|
||||
if (diff < minDiff) {
|
||||
minDiff = diff;
|
||||
nextBid = bid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nextBid;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { Config } from '../entities/configs.entity';
|
||||
|
||||
@Injectable()
|
||||
export class ConfigsService {
|
||||
public static CONFIG_KEYS = {
|
||||
REFRESH_TOOL_TIME: 'REFRESH_TOOL_TIME',
|
||||
};
|
||||
|
||||
constructor(
|
||||
@InjectRepository(Config)
|
||||
readonly configRepo: Repository<Config>,
|
||||
private eventEmitter: EventEmitter2,
|
||||
) {}
|
||||
|
||||
async getConfig(key_name: keyof typeof ConfigsService.CONFIG_KEYS) {
|
||||
return await this.configRepo.findOne({ where: { key_name } }) || null;
|
||||
}
|
||||
|
||||
async setConfig(key_name: keyof typeof ConfigsService.CONFIG_KEYS, value: string) {
|
||||
return await this.configRepo.upsert(
|
||||
{ key_name, value },
|
||||
['key_name']
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { Cron, CronExpression } from '@nestjs/schedule';
|
||||
import { IsNull, Not } from 'typeorm';
|
||||
import { BidsService } from './bids.service';
|
||||
import { isTimeReached, subtractMinutes, subtractSeconds } from '@/ultils';
|
||||
import { ConfigsService } from './configs.service';
|
||||
import { DashboardService } from './dashboard.service';
|
||||
@Injectable()
|
||||
export class TasksService {
|
||||
private readonly logger = new Logger(TasksService.name);
|
||||
|
||||
constructor(
|
||||
private readonly bidsService: BidsService,
|
||||
private readonly configsService: ConfigsService,
|
||||
private readonly dashboadService: DashboardService,
|
||||
) {}
|
||||
|
||||
@Cron(CronExpression.EVERY_MINUTE)
|
||||
async handleCron() {
|
||||
const nextBid = await this.bidsService.getNextBid();
|
||||
|
||||
if (!nextBid) return;
|
||||
|
||||
const timeReset = subtractMinutes(nextBid.close_time, 20);
|
||||
|
||||
const timeToTracking = subtractSeconds(
|
||||
nextBid.close_time,
|
||||
nextBid.web_bid.early_tracking_seconds,
|
||||
);
|
||||
|
||||
if (!isTimeReached(timeReset) || isTimeReached(timeToTracking)) {
|
||||
console.log('Reset not allowed at this time');
|
||||
return;
|
||||
}
|
||||
|
||||
const lastestResetToolTime =
|
||||
await this.configsService.getConfig('REFRESH_TOOL_TIME');
|
||||
|
||||
if (lastestResetToolTime?.value) {
|
||||
const lastReset = Date.parse(lastestResetToolTime.value);
|
||||
const now = Date.now();
|
||||
|
||||
const diffInMs = now - lastReset;
|
||||
const diffInHours = diffInMs / (1000 * 60 * 60);
|
||||
const minimumHours = 2;
|
||||
|
||||
if (diffInHours < minimumHours) {
|
||||
console.log(`Last reset was less than ${minimumHours} hours ago`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Proceeding to reset tool for next bid:', nextBid);
|
||||
|
||||
await this.dashboadService.resetTool();
|
||||
|
||||
|
||||
const time = new Date().toUTCString()
|
||||
|
||||
await this.configsService.setConfig(
|
||||
'REFRESH_TOOL_TIME',
|
||||
time,
|
||||
);
|
||||
|
||||
console.log('Reset successfully at: ' + time);
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -109,18 +109,40 @@ export class NotificationService {
|
|||
send_to: JSON.stringify(sendToData),
|
||||
});
|
||||
|
||||
await this.sendMessageRepo.save({
|
||||
bid: { id: bid.id },
|
||||
message: notification.message,
|
||||
type: bid.status,
|
||||
});
|
||||
try {
|
||||
const prevAnyMessage = await this.sendMessageRepo.findOne({
|
||||
where: {
|
||||
bid: { id: bid.id },
|
||||
message: notification.message,
|
||||
type: bid.status,
|
||||
max_price: bid.max_price,
|
||||
reserve_price: bid.reserve_price
|
||||
},
|
||||
});
|
||||
|
||||
this.eventEmitter.emit(NAME_EVENTS.BID_STATUS, {
|
||||
bid: {
|
||||
...bid,
|
||||
status: 'out-bid',
|
||||
},
|
||||
notification,
|
||||
});
|
||||
if (prevAnyMessage) return;
|
||||
|
||||
await this.sendMessageRepo.save({
|
||||
bid: { id: bid.id },
|
||||
message: notification.message,
|
||||
type: bid.status,
|
||||
max_price: bid.max_price,
|
||||
reserve_price: bid.reserve_price
|
||||
});
|
||||
|
||||
this.eventEmitter.emit(NAME_EVENTS.BID_STATUS, {
|
||||
bid: {
|
||||
...bid,
|
||||
status: 'out-bid',
|
||||
},
|
||||
notification,
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(
|
||||
'%csrc/modules/notification/notification.service.ts:131 Error',
|
||||
'color: #007acc;',
|
||||
Error,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,12 @@ export function subtractMinutes(timeStr: string, minutes: number) {
|
|||
return date.toISOString(); // Trả về dạng chuẩn ISO
|
||||
}
|
||||
|
||||
export function subtractSeconds(time: string, seconds: number) {
|
||||
const date = new Date(time);
|
||||
date.setSeconds(date.getSeconds() - seconds);
|
||||
return date.toUTCString();
|
||||
}
|
||||
|
||||
export function isTimeReached(targetTime: string) {
|
||||
const targetDate = new Date(targetTime);
|
||||
const now = new Date();
|
||||
|
|
|
|||
|
|
@ -184,6 +184,8 @@ const tracking = async () => {
|
|||
|
||||
// Kiểm tra URL và điều hướng nếu cần
|
||||
if ((await productTab.page_context.url()) !== productTab.url) {
|
||||
if (global[`IS_PLACE_BID-${productTab.id}`]) return;
|
||||
|
||||
console.log(
|
||||
`🔄 Redirecting to new URL for Product ID: ${productTab.id}`
|
||||
);
|
||||
|
|
|
|||
|
|
@ -98,9 +98,15 @@ export class ApiBid extends Bid {
|
|||
if (this.snapshot_at) {
|
||||
const nearestCloseTime = findNearestClosingChild(this);
|
||||
|
||||
if (!nearestCloseTime) {
|
||||
console.log(`❌ [${this.id}] No nearest closing child found.`);
|
||||
return false;
|
||||
if (!nearestCloseTime || this.children.some((item) => !item.close_time)) {
|
||||
console.log(`🔌 [${this.id}] Connecting to puppeteer...`);
|
||||
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;
|
||||
|
|
@ -137,10 +143,13 @@ export class ApiBid extends Bid {
|
|||
// Nếu chưa có ảnh chụp working => tab not lazy
|
||||
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);
|
||||
|
||||
// Nếu không có nearest close => tab lazy
|
||||
if (!nearestCloseTime) return true;
|
||||
// Nếu không có nearest close => tab not lazy
|
||||
if (!nearestCloseTime) return false;
|
||||
|
||||
const { close_time } = nearestCloseTime;
|
||||
|
||||
|
|
|
|||
|
|
@ -125,37 +125,37 @@ export class GrayApiBid extends ApiBid {
|
|||
(bid) => !this.children_processing.some((item) => item.model === bid.Sku)
|
||||
);
|
||||
|
||||
const handleChildren = this.children.filter((item) =>
|
||||
bidOutLots.some((i) => i.Sku === item.model)
|
||||
);
|
||||
// const handleChildren = this.children.filter((item) =>
|
||||
// bidOutLots.some((i) => i.Sku === item.model)
|
||||
// );
|
||||
|
||||
console.log({
|
||||
handleChildren,
|
||||
children_processing: this.children_processing,
|
||||
data,
|
||||
bidOutLots,
|
||||
});
|
||||
// console.log({
|
||||
// handleChildren,
|
||||
// children_processing: this.children_processing,
|
||||
// data,
|
||||
// bidOutLots,
|
||||
// });
|
||||
|
||||
for (const product_tab of handleChildren) {
|
||||
if (!isTimeReached(product_tab.start_bid_time)) {
|
||||
console.log(
|
||||
`❌ [${this.id}] It's not time yet ID: ${product_tab.id} continue waiting...`
|
||||
);
|
||||
return;
|
||||
}
|
||||
// for (const product_tab of handleChildren) {
|
||||
// if (!isTimeReached(product_tab.start_bid_time)) {
|
||||
// console.log(
|
||||
// `❌ [${this.id}] It's not time yet ID: ${product_tab.id} continue waiting...`
|
||||
// );
|
||||
// return;
|
||||
// }
|
||||
|
||||
this.children_processing.push(product_tab);
|
||||
// this.children_processing.push(product_tab);
|
||||
|
||||
if (!product_tab.page_context) {
|
||||
await product_tab.puppeteer_connect();
|
||||
}
|
||||
// if (!product_tab.page_context) {
|
||||
// await product_tab.puppeteer_connect();
|
||||
// }
|
||||
|
||||
await product_tab.action();
|
||||
// await product_tab.action();
|
||||
|
||||
this.children_processing = this.children_processing.filter(
|
||||
(item) => item.id !== product_tab.id
|
||||
);
|
||||
}
|
||||
// this.children_processing = this.children_processing.filter(
|
||||
// (item) => item.id !== product_tab.id
|
||||
// );
|
||||
// }
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
|
|
@ -127,43 +82,61 @@ export class GraysProductBid extends ProductBid {
|
|||
return { result: false, close_time };
|
||||
}
|
||||
|
||||
async handleWritePrice(page, bid_price) {
|
||||
await page.type("#price", String(bid_price));
|
||||
await delay(500);
|
||||
}
|
||||
|
||||
async placeBid(page) {
|
||||
async placeBid() {
|
||||
try {
|
||||
await page.click("#bid-type-standard");
|
||||
await delay(500);
|
||||
await this.page_context.evaluate(() => {
|
||||
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 page.waitForSelector("button", { timeout: 5000 });
|
||||
await this.page_context.waitForSelector("button", { timeout: 5000 });
|
||||
|
||||
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(
|
||||
page,
|
||||
this,
|
||||
"bid-success",
|
||||
CONSTANTS.TYPE_IMAGE.SUCCESS
|
||||
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();
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.log(`❌ [${this.id}] Timeout to loading`);
|
||||
await takeSnapshot(page, this, "timeout to loading");
|
||||
console.log(`❌ [${this.id}] Error in placeBid: ${error.message}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async handleReturnProductPage(page) {
|
||||
await page.goto(this.url);
|
||||
async handleReturnProductPage() {
|
||||
await this.page_context.goto(this.url);
|
||||
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 () => {
|
||||
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;
|
||||
// 📌 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 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);
|
||||
await this.handlePlaceBid();
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`🚨 [${this.id}] Error navigating the page: ${error.message}`
|
||||
|
|
|
|||
Loading…
Reference in New Issue