Merge pull request 'update time tracking for api bid' (#29) from zelda.by-pass-langtons-prev-code into main
Reviewed-on: #29
This commit is contained in:
commit
c19400ef66
|
|
@ -2,9 +2,8 @@
|
|||
import { LoadingOverlay, Modal, ModalProps, Table } from '@mantine/core';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { getDetailBidHistories } from '../../apis/bid-histories';
|
||||
import { extractNumber } from '../../lib/table/ultils';
|
||||
import { IBid } from '../../system/type';
|
||||
import { formatTime } from '../../utils';
|
||||
import { extractNumber, formatTime } from '../../utils';
|
||||
|
||||
export interface IShowHistoriesBidGraysApiModalProps extends ModalProps {
|
||||
data: IBid | null;
|
||||
|
|
|
|||
|
|
@ -6,9 +6,8 @@ import { Socket } from "socket.io-client";
|
|||
import { getImagesWorking } from "../../apis/bid";
|
||||
import { useStatusToolStore } from "../../lib/zustand/use-status-tool-store";
|
||||
import { IBid, IWebBid } from "../../system/type";
|
||||
import { cn, extractDomainSmart, stringToColor } from "../../utils";
|
||||
import { cn, extractDomainSmart, findNearestClosingChild, isTimeReached, stringToColor, subtractSeconds } from "../../utils";
|
||||
import ShowImageModal from "./show-image-modal";
|
||||
import { isTimeReached, subtractSeconds } from "../../lib/table/ultils";
|
||||
export interface IWorkingPageProps {
|
||||
data: (IBid | IWebBid) & { type: string };
|
||||
socket: Socket;
|
||||
|
|
@ -111,6 +110,11 @@ export default function WorkingPage({ data, socket }: IWorkingPageProps) {
|
|||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
|
||||
if(!isIBid(data)){
|
||||
console.log(data)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box
|
||||
|
|
@ -140,10 +144,17 @@ export default function WorkingPage({ data, socket }: IWorkingPageProps) {
|
|||
<Text className="text-xs tracking-wide">{`Current price: $${data.current_price}`}</Text>
|
||||
)}
|
||||
|
||||
|
||||
|
||||
<Text className="text-sm italic opacity-80">
|
||||
{moment(lastUpdate).format("HH:mm:ss DD/MM/YYYY")}
|
||||
</Text>
|
||||
|
||||
|
||||
{!isIBid(data) && <Tooltip label={'Time to tracking'}><Text>{`TT: ${moment(subtractSeconds(findNearestClosingChild(data)?.close_time || '', data.early_tracking_seconds)).format(
|
||||
"HH:mm:ss DD/MM/YYYY"
|
||||
)}`}</Text></Tooltip>}
|
||||
|
||||
<Box className="flex items-center gap-3">
|
||||
{isIBid(data) && (
|
||||
<Tooltip label={'Close time'}>
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import { z } from "zod";
|
|||
import { createWebBid, updateWebBid } from "../../apis/web-bid";
|
||||
import { useConfirmStore } from "../../lib/zustand/use-confirm";
|
||||
import { IWebBid } from "../../system/type";
|
||||
import { extractDomain } from "../../utils";
|
||||
import { extractDomain, formatTimeFromMinutes } from "../../utils";
|
||||
export interface IWebBidModelProps extends ModalProps {
|
||||
data: IWebBid | null;
|
||||
onUpdated?: () => void;
|
||||
|
|
@ -158,8 +158,8 @@ export default function WebBidModal({
|
|||
className="col-span-2"
|
||||
size="sm"
|
||||
label={`Arrival offset seconds (${
|
||||
form.getValues()["arrival_offset_seconds"] / 60
|
||||
} minutes)`}
|
||||
formatTimeFromMinutes(form.getValues()["arrival_offset_seconds"] / 60)
|
||||
})`}
|
||||
placeholder="msg: 300"
|
||||
{...form.getInputProps("arrival_offset_seconds")}
|
||||
/>
|
||||
|
|
@ -168,8 +168,8 @@ export default function WebBidModal({
|
|||
className="col-span-2"
|
||||
size="sm"
|
||||
label={`Early tracking seconds (${
|
||||
form.getValues()["early_tracking_seconds"] / 60
|
||||
} minutes)`}
|
||||
formatTimeFromMinutes(form.getValues()["early_tracking_seconds"] / 60)
|
||||
})`}
|
||||
placeholder="msg: 600"
|
||||
{...form.getInputProps("early_tracking_seconds")}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -117,28 +117,4 @@ export const removeFalsy = (data: { [key: string]: string | number }) => {
|
|||
}, {} as { [key: string]: string | number });
|
||||
};
|
||||
|
||||
export function extractNumber(str: string) {
|
||||
const match = str.match(/\d+(\.\d+)?/);
|
||||
return match ? parseFloat(match[0]) : null;
|
||||
}
|
||||
|
||||
export function subtractMinutes(time: string, minutes: number) {
|
||||
const date = new Date(time);
|
||||
date.setMinutes(date.getMinutes() - minutes);
|
||||
return date.toUTCString();
|
||||
}
|
||||
|
||||
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) {
|
||||
if (!targetTime) return false;
|
||||
|
||||
const targetDate = new Date(targetTime);
|
||||
const now = new Date();
|
||||
|
||||
return now >= targetDate;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { ActionIcon, Badge, Box, Menu, Text, Tooltip } from "@mantine/core";
|
||||
import { ActionIcon, Anchor, Badge, Box, Menu, Text, Tooltip } from "@mantine/core";
|
||||
import { useDisclosure } from "@mantine/hooks";
|
||||
import {
|
||||
IconAd,
|
||||
|
|
@ -7,7 +7,7 @@ import {
|
|||
IconHammer,
|
||||
IconHistory,
|
||||
IconMenu,
|
||||
IconTrash,
|
||||
IconTrash
|
||||
} from "@tabler/icons-react";
|
||||
import _ from "lodash";
|
||||
import { useMemo, useRef, useState } from "react";
|
||||
|
|
@ -51,6 +51,11 @@ export default function Bids() {
|
|||
key: "name",
|
||||
title: "Name",
|
||||
typeFilter: "text",
|
||||
renderRow(row) {
|
||||
|
||||
|
||||
return <Anchor className="text-[14px]" href={row.url} size="sm" style={{color: 'inherit'}}>{row.name}</Anchor>
|
||||
},
|
||||
},
|
||||
{
|
||||
key: "web_bid",
|
||||
|
|
@ -187,9 +192,7 @@ export default function Bids() {
|
|||
const table = useMemo(() => {
|
||||
return (
|
||||
<Table
|
||||
onClickRow={(row) => {
|
||||
window.open(row.url, "_blank");
|
||||
}}
|
||||
|
||||
tableChildProps={{
|
||||
trbody: {
|
||||
className: "cursor-pointer",
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ export interface IWebBid extends ITimestamp {
|
|||
active: boolean;
|
||||
arrival_offset_seconds: number;
|
||||
early_tracking_seconds: number;
|
||||
snapshot_at: string | null
|
||||
children: IBid[];
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ import { clsx, type ClassValue } from "clsx";
|
|||
import { twMerge } from "tailwind-merge";
|
||||
import moment from "moment";
|
||||
import { IWebBid } from "../system/type";
|
||||
import _ from 'lodash'
|
||||
|
||||
export function cn(...args: ClassValue[]) {
|
||||
return twMerge(clsx(args));
|
||||
}
|
||||
|
|
@ -201,3 +203,63 @@ export function extractDomainSmart(url: string) {
|
|||
return url;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export function findNearestClosingChild(webBid: IWebBid) {
|
||||
const now = Date.now();
|
||||
|
||||
const validChildren = webBid.children.filter(
|
||||
(child) => child.close_time && !isNaN(new Date(child.close_time).getTime())
|
||||
);
|
||||
|
||||
if (validChildren.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const nearestChild = _.minBy(validChildren, (child) => {
|
||||
return Math.abs(new Date(child.close_time!).getTime() - now);
|
||||
});
|
||||
|
||||
return nearestChild || null;
|
||||
}
|
||||
|
||||
export function extractNumber(str: string) {
|
||||
const match = str.match(/\d+(\.\d+)?/);
|
||||
return match ? parseFloat(match[0]) : null;
|
||||
}
|
||||
|
||||
export function subtractMinutes(time: string, minutes: number) {
|
||||
const date = new Date(time);
|
||||
date.setMinutes(date.getMinutes() - minutes);
|
||||
return date.toUTCString();
|
||||
}
|
||||
|
||||
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) {
|
||||
if (!targetTime) return false;
|
||||
|
||||
const targetDate = new Date(targetTime);
|
||||
const now = new Date();
|
||||
|
||||
return now >= targetDate;
|
||||
}
|
||||
|
||||
export function formatTimeFromMinutes(minutes: number): string {
|
||||
// Tính ngày, giờ, phút từ số phút
|
||||
const days = Math.floor(minutes / (60 * 24));
|
||||
const hours = Math.floor((minutes % (60 * 24)) / 60);
|
||||
const mins = minutes % 60;
|
||||
|
||||
let result = '';
|
||||
|
||||
if (days > 0) result += `${days} ${days > 1? 'days' :'day'} `;
|
||||
if (hours > 0) result += `${hours} ${hours > 1 ? 'hours' : 'hour'} `;
|
||||
if (mins > 0 || result === '') result += `${mins} minutes`;
|
||||
|
||||
return result.trim();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
/node_modules
|
||||
/build
|
||||
/public
|
||||
/bot-data
|
||||
|
||||
# Logs
|
||||
logs
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
{"createdAt":1746603511532}
|
||||
{"createdAt":1747011314493}
|
||||
|
|
@ -23,6 +23,9 @@ export class WebBid extends Timestamp {
|
|||
@Column({ default: 600 })
|
||||
early_tracking_seconds: number;
|
||||
|
||||
@Column({ default: null })
|
||||
snapshot_at: Date | null;
|
||||
|
||||
@Column({ default: null, nullable: true })
|
||||
@Exclude()
|
||||
password: string;
|
||||
|
|
|
|||
|
|
@ -228,7 +228,10 @@ export class BidsService {
|
|||
if (!bid.close_time && !bid.start_bid_time) {
|
||||
// Thiết lập thời gian bắt đầu là 5 phút trước khi đóng
|
||||
// bid.start_bid_time = new Date().toUTCString();
|
||||
bid.start_bid_time = subtractMinutes(close_time, bid.web_bid.arrival_offset_seconds/ 60);
|
||||
bid.start_bid_time = subtractMinutes(
|
||||
close_time,
|
||||
bid.web_bid.arrival_offset_seconds / 60,
|
||||
);
|
||||
}
|
||||
|
||||
// Kiểm tra nếu thời gian đóng bid đã đạt tới (tức phiên đấu giá đã kết thúc)
|
||||
|
|
@ -274,7 +277,7 @@ export class BidsService {
|
|||
const result = await this.bidsRepo.save({
|
||||
...bid,
|
||||
...data,
|
||||
current_price: Math.max(data?.current_price || 0, bid.current_price),
|
||||
current_price: Math.max(data?.current_price || 0, bid.current_price),
|
||||
updated_at: new Date(), // Cập nhật timestamp
|
||||
});
|
||||
|
||||
|
|
@ -422,6 +425,11 @@ export class BidsService {
|
|||
}),
|
||||
);
|
||||
|
||||
// update time snapshot for API BID
|
||||
if (type === 'API_BID') {
|
||||
this.webBidsService.webBidRepo.update(id, { snapshot_at: new Date() });
|
||||
}
|
||||
|
||||
this.eventEmitter.emit(`working`, {
|
||||
status: 're-update',
|
||||
id,
|
||||
|
|
@ -510,11 +518,9 @@ export class BidsService {
|
|||
return AppResponse.toResponse(files);
|
||||
}
|
||||
|
||||
async emitLoginStatus(data: ClientUpdateLoginStatusDto){
|
||||
async emitLoginStatus(data: ClientUpdateLoginStatusDto) {
|
||||
this.eventEmitter.emit(Event.statusLogin(data.data), data);
|
||||
|
||||
this.eventEmitter.emit(Event.statusLogin(data.data), data)
|
||||
|
||||
|
||||
return AppResponse.toResponse(true)
|
||||
return AppResponse.toResponse(true);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import browser from "./system/browser.js";
|
|||
import configs from "./system/config.js";
|
||||
import {
|
||||
delay,
|
||||
isPageAvailable,
|
||||
findNearestClosingChild,
|
||||
isTimeReached,
|
||||
safeClosePage,
|
||||
subtractSeconds,
|
||||
|
|
@ -270,20 +270,20 @@ const clearLazyTab = async () => {
|
|||
// product tabs
|
||||
const productTabs = _.flatMap(MANAGER_BIDS, "children");
|
||||
|
||||
for (const item of [...productTabs, ...MANAGER_BIDS]) {
|
||||
if (!item.page_context) continue;
|
||||
// for (const item of [...productTabs, ...MANAGER_BIDS]) {
|
||||
// if (!item.page_context) continue;
|
||||
|
||||
try {
|
||||
const avalableResult = await isPageAvailable(item.page_context);
|
||||
// try {
|
||||
// const avalableResult = await isPageAvailable(item.page_context);
|
||||
|
||||
if (!avalableResult) {
|
||||
await safeClosePage(item);
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn("⚠️ Error checking page_context.title()", e.message);
|
||||
await safeClosePage(item);
|
||||
}
|
||||
}
|
||||
// if (!avalableResult) {
|
||||
// await safeClosePage(item);
|
||||
// }
|
||||
// } catch (e) {
|
||||
// console.warn("⚠️ Error checking page_context.title()", e.message);
|
||||
// await safeClosePage(item);
|
||||
// }
|
||||
// }
|
||||
|
||||
for (const page of pages) {
|
||||
try {
|
||||
|
|
@ -303,13 +303,6 @@ const clearLazyTab = async () => {
|
|||
productTab?.web_bid?.early_tracking_seconds || 0
|
||||
);
|
||||
|
||||
console.log("%cindex.js:291 {object}", "color: #007acc;", {
|
||||
earlyTrackingTime,
|
||||
it_time: isTimeReached(earlyTrackingTime),
|
||||
close_time: productTab.close_time,
|
||||
tracking_time: productTab?.web_bid?.early_tracking_seconds,
|
||||
});
|
||||
|
||||
if (!isTimeReached(earlyTrackingTime)) {
|
||||
await safeClosePage(productTab);
|
||||
console.log(`🛑 Unused page detected: ${pageUrl}`);
|
||||
|
|
@ -354,6 +347,15 @@ const clearLazyTab = async () => {
|
|||
console.warn(`⚠️ Error handling page: ${pageErr.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Delete lazy tracking page
|
||||
Promise.allSettled(
|
||||
MANAGER_BIDS.map(async (item) => {
|
||||
if (await item.isLazy()) {
|
||||
safeClosePage(item);
|
||||
}
|
||||
})
|
||||
);
|
||||
} catch (err) {
|
||||
console.error("❌ Error in clearLazyTab:", err.message);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,10 +5,12 @@ import browser from "../system/browser.js";
|
|||
import CONSTANTS from "../system/constants.js";
|
||||
import {
|
||||
findEarlyLoginTime,
|
||||
findNearestClosingChild,
|
||||
getPathLocalData,
|
||||
getPathProfile,
|
||||
isTimeReached,
|
||||
sanitizeFileName,
|
||||
subtractSeconds,
|
||||
} from "../system/utils.js";
|
||||
import { Bid } from "./bid.js";
|
||||
|
||||
|
|
@ -24,6 +26,8 @@ export class ApiBid extends Bid {
|
|||
// browser_context;
|
||||
username;
|
||||
password;
|
||||
early_tracking_seconds;
|
||||
snapshot_at;
|
||||
|
||||
constructor({
|
||||
url,
|
||||
|
|
@ -35,6 +39,8 @@ export class ApiBid extends Bid {
|
|||
updated_at,
|
||||
origin_url,
|
||||
active,
|
||||
early_tracking_seconds,
|
||||
snapshot_at,
|
||||
}) {
|
||||
super(BID_TYPE.API_BID, url);
|
||||
|
||||
|
|
@ -45,6 +51,8 @@ export class ApiBid extends Bid {
|
|||
this.active = active;
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
this.early_tracking_seconds = early_tracking_seconds;
|
||||
this.snapshot_at = snapshot_at;
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
|
|
@ -58,6 +66,8 @@ export class ApiBid extends Bid {
|
|||
updated_at,
|
||||
origin_url,
|
||||
active,
|
||||
early_tracking_seconds,
|
||||
snapshot_at,
|
||||
}) {
|
||||
this.created_at = created_at;
|
||||
this.updated_at = updated_at;
|
||||
|
|
@ -67,6 +77,8 @@ export class ApiBid extends Bid {
|
|||
this.username = username;
|
||||
this.password = password;
|
||||
this.url = url;
|
||||
this.early_tracking_seconds = early_tracking_seconds;
|
||||
this.snapshot_at = snapshot_at;
|
||||
}
|
||||
|
||||
puppeteer_connect = async () => {
|
||||
|
|
@ -79,12 +91,81 @@ export class ApiBid extends Bid {
|
|||
await this.restoreContext();
|
||||
};
|
||||
|
||||
async handlePrevListen() {
|
||||
console.log(`👂 [${this.id}] Start handlePrevListen...`);
|
||||
|
||||
// Chỉ bắt đầu check khi ảnh đã được chụp
|
||||
if (this.snapshot_at) {
|
||||
const nearestCloseTime = findNearestClosingChild(this);
|
||||
|
||||
if (!nearestCloseTime) {
|
||||
console.log(`❌ [${this.id}] No nearest closing child found.`);
|
||||
return false;
|
||||
}
|
||||
|
||||
const { close_time } = nearestCloseTime;
|
||||
console.log(`📅 [${this.id}] Nearest close_time: ${close_time}`);
|
||||
|
||||
const timeToTracking = subtractSeconds(
|
||||
close_time,
|
||||
this.early_tracking_seconds || 0
|
||||
);
|
||||
|
||||
console.log(
|
||||
`🕰️ [${this.id}] Time to tracking: ${new Date(
|
||||
timeToTracking
|
||||
).toISOString()}`
|
||||
);
|
||||
|
||||
if (!isTimeReached(timeToTracking)) {
|
||||
console.log(`⏳ [${this.id}] Not time to track yet.`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
async isLazy() {
|
||||
// Nếu chưa có ảnh chụp working => tab not lazy
|
||||
if (!this.snapshot_at) return false;
|
||||
|
||||
const nearestCloseTime = findNearestClosingChild(this);
|
||||
|
||||
// Nếu không có nearest close => tab lazy
|
||||
if (!nearestCloseTime) return true;
|
||||
|
||||
const { close_time } = nearestCloseTime;
|
||||
|
||||
const timeToTracking = subtractSeconds(
|
||||
close_time,
|
||||
this.early_tracking_seconds || 0
|
||||
);
|
||||
|
||||
// Nếu chưa đến giờ tracking => tab lazy
|
||||
if (!isTimeReached(timeToTracking)) return true;
|
||||
|
||||
// Các trường hợp còn lại => not lazy
|
||||
return false;
|
||||
}
|
||||
|
||||
listen_events = async () => {
|
||||
if (this.page_context) return;
|
||||
|
||||
await this.puppeteer_connect();
|
||||
// await this.puppeteer_connect();
|
||||
|
||||
await this.action();
|
||||
// await this.action();
|
||||
|
||||
const results = await this.handlePrevListen();
|
||||
|
||||
if (!results) return;
|
||||
|
||||
await this.saveContext();
|
||||
};
|
||||
|
|
@ -107,16 +188,16 @@ export class ApiBid extends Bid {
|
|||
|
||||
if (!fs.existsSync(dirPath)) {
|
||||
fs.mkdirSync(dirPath, { recursive: true });
|
||||
console.log(`📂 Save at folder: ${dirPath}`);
|
||||
console.log(`📂 [${this.id}] Save at folder: ${dirPath}`);
|
||||
}
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(dirPath, sanitizeFileName(this.origin_url) + ".json"),
|
||||
JSON.stringify(contextData, null, 2)
|
||||
);
|
||||
console.log("✅ Context saved!");
|
||||
console.log(`✅ [${this.id}] Context saved!`);
|
||||
} catch (error) {
|
||||
console.log("Save Context: ", error.message);
|
||||
console.log(`[${this.id}] Save Context: , ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -132,7 +213,7 @@ export class ApiBid extends Bid {
|
|||
// Restore Cookies
|
||||
await this.page_context.setCookie(...contextData.cookies);
|
||||
|
||||
console.log("🔄 Context restored!");
|
||||
console.log(`🔄 [${this.id}] Context restored!`);
|
||||
}
|
||||
|
||||
async onCloseLogin() {}
|
||||
|
|
|
|||
|
|
@ -113,7 +113,7 @@ export class LangtonsApiBid extends ApiBid {
|
|||
return { success: false, error: error.toString() };
|
||||
}
|
||||
},
|
||||
{ usernam: this.username, password: this.password },
|
||||
{ username: this.username, password: this.password },
|
||||
csrfToken
|
||||
); // truyền biến vào page context
|
||||
|
||||
|
|
@ -158,34 +158,14 @@ export class LangtonsApiBid extends ApiBid {
|
|||
}
|
||||
}
|
||||
|
||||
async submitPrevCode() {
|
||||
async submitCode({ name, code }) {
|
||||
try {
|
||||
const prevCodeData = await this.loadCodeFromLocal();
|
||||
|
||||
if (!prevCodeData) return false;
|
||||
|
||||
await this.page_context.goto(
|
||||
"https://www.langtons.com.au/account/mfalogin?rurl=5"
|
||||
);
|
||||
|
||||
await this.page_context.waitForNavigation({
|
||||
timeout: 8000,
|
||||
waitUntil: "domcontentloaded",
|
||||
});
|
||||
|
||||
if (!(await page.type("#code", code, { delay: 120 }))) {
|
||||
await this.clearCodeFromLocal();
|
||||
|
||||
await this.page_context.goto(configs.WEB_URLS.LANGTONS.LOGIN_URL);
|
||||
return false;
|
||||
}
|
||||
|
||||
const csrfToken = await this.getCsrfToken();
|
||||
|
||||
if (!csrfToken) return false;
|
||||
|
||||
const responsePrevCode = await this.callSubmitPrevCodeApi(
|
||||
prevCodeData.code,
|
||||
code,
|
||||
csrfToken
|
||||
);
|
||||
|
||||
|
|
@ -199,9 +179,6 @@ export class LangtonsApiBid extends ApiBid {
|
|||
return false;
|
||||
}
|
||||
|
||||
// submit tiếp api login....
|
||||
await this.page_context.goto(this.url);
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error(`❌ [${this.id}] Error submitPrevCode:`, error);
|
||||
|
|
@ -310,24 +287,31 @@ export class LangtonsApiBid extends ApiBid {
|
|||
// save code to local
|
||||
// await this.saveCodeToLocal({ name, code });
|
||||
|
||||
// ⌨ Enter verification code
|
||||
console.log(`✍ [${this.id}] Entering verification code...`);
|
||||
await page.type("#code", code, { delay: 120 });
|
||||
// // ⌨ Enter verification code
|
||||
// console.log(`✍ [${this.id}] Entering verification code...`);
|
||||
// await page.type("#code", code, { delay: 120 });
|
||||
|
||||
// 🚀 Click the verification confirmation button
|
||||
console.log(
|
||||
`🔘 [${this.id}] Clicking the verification confirmation button`
|
||||
);
|
||||
await page.click(".btn.btn-block.btn-primary", { delay: 90 });
|
||||
// // 🚀 Click the verification confirmation button
|
||||
// console.log(
|
||||
// `🔘 [${this.id}] Clicking the verification confirmation button`
|
||||
// );
|
||||
// await page.click(".btn.btn-block.btn-primary", { delay: 90 });
|
||||
|
||||
// ⏳ Wait for navigation after verification
|
||||
console.log(
|
||||
`⏳ [${this.id}] Waiting for navigation after verification...`
|
||||
);
|
||||
await page.waitForNavigation({
|
||||
timeout: 15000,
|
||||
waitUntil: "domcontentloaded",
|
||||
});
|
||||
const reuslt = await this.submitCode({ name, code });
|
||||
|
||||
if (!reuslt) {
|
||||
console.log(`[${this.id}] Wrote verifi code failure`);
|
||||
return;
|
||||
}
|
||||
|
||||
// // ⏳ Wait for navigation after verification
|
||||
// console.log(
|
||||
// `⏳ [${this.id}] Waiting for navigation after verification...`
|
||||
// );
|
||||
// await page.waitForNavigation({
|
||||
// timeout: 15000,
|
||||
// waitUntil: "domcontentloaded",
|
||||
// });
|
||||
|
||||
await page.goto(this.url, { waitUntil: "networkidle2" });
|
||||
|
||||
|
|
@ -441,8 +425,11 @@ export class LangtonsApiBid extends ApiBid {
|
|||
listen_events = async () => {
|
||||
if (this.page_context) return;
|
||||
|
||||
await this.puppeteer_connect();
|
||||
await this.action();
|
||||
// await this.puppeteer_connect();
|
||||
// await this.action();
|
||||
const results = await this.handlePrevListen();
|
||||
|
||||
if (!results) return;
|
||||
|
||||
this.reloadInterval = setInterval(async () => {
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -1,258 +1,300 @@
|
|||
import fs from 'fs';
|
||||
import configs from '../../system/config.js';
|
||||
import { delay, getPathProfile, safeClosePage } from '../../system/utils.js';
|
||||
import { ApiBid } from '../api-bid.js';
|
||||
import fs from "fs";
|
||||
import configs from "../../system/config.js";
|
||||
import { delay, getPathProfile, safeClosePage } from "../../system/utils.js";
|
||||
import { ApiBid } from "../api-bid.js";
|
||||
|
||||
export class LawsonsApiBid extends ApiBid {
|
||||
reloadInterval = null;
|
||||
constructor({ ...prev }) {
|
||||
super(prev);
|
||||
reloadInterval = null;
|
||||
constructor({ ...prev }) {
|
||||
super(prev);
|
||||
}
|
||||
|
||||
waitVerifyData = async () =>
|
||||
new Promise((rev, rej) => {
|
||||
// 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 2 minute.`
|
||||
)
|
||||
);
|
||||
}, 120 * 1000); // 60 giây
|
||||
|
||||
global.socket.on(`verify-code.${this.origin_url}`, async (data) => {
|
||||
console.log(`📢 [${this.id}] VERIFY CODE:`, data);
|
||||
clearTimeout(timeout); // Hủy timeout vì đã nhận được mã
|
||||
global.socket.off(`verify-code.${this.origin_url}`); // Xóa listener tránh lặp lại
|
||||
rev(data); // Resolve với dữ liệu nhận được
|
||||
});
|
||||
});
|
||||
|
||||
async isLogin() {
|
||||
if (!this.page_context) return false;
|
||||
|
||||
const filePath = getPathProfile(this.origin_url);
|
||||
|
||||
return (
|
||||
!(await this.page_context.$("#emailLogin")) && fs.existsSync(filePath)
|
||||
);
|
||||
}
|
||||
|
||||
waitVerifyData = async () =>
|
||||
new Promise((rev, rej) => {
|
||||
// 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
|
||||
|
||||
global.socket.on(`verify-code.${this.origin_url}`, async (data) => {
|
||||
console.log(`📢 [${this.id}] VERIFY CODE:`, data);
|
||||
clearTimeout(timeout); // Hủy timeout vì đã nhận được mã
|
||||
global.socket.off(`verify-code.${this.origin_url}`); // Xóa listener tránh lặp lại
|
||||
rev(data); // Resolve với dữ liệu nhận được
|
||||
});
|
||||
});
|
||||
|
||||
async enterOTP(otp) {
|
||||
try {
|
||||
// Selector cho tất cả các input OTP
|
||||
const inputSelector = ".MuiDialog-container .container input";
|
||||
|
||||
// Chờ cho các input OTP xuất hiện
|
||||
await this.page_context.waitForSelector(inputSelector, { timeout: 8000 });
|
||||
|
||||
// Lấy tất cả các input OTP
|
||||
const inputs = await this.page_context.$$(inputSelector);
|
||||
|
||||
// Kiểm tra nếu có đúng 6 trường input
|
||||
if (inputs.length === 6 && otp.length === 6) {
|
||||
// Nhập mỗi ký tự của OTP vào các input tương ứng
|
||||
for (let i = 0; i < 6; i++) {
|
||||
await inputs[i].type(otp[i], { delay: 100 });
|
||||
}
|
||||
console.log(`✅ OTP entered successfully: ${otp}`);
|
||||
} else {
|
||||
console.error("❌ Invalid OTP or input fields count");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("❌ Error entering OTP:", error);
|
||||
}
|
||||
}
|
||||
|
||||
async waitToTwoVerify() {
|
||||
try {
|
||||
if (!this.page_context) return false;
|
||||
|
||||
// Selector của các phần tử trên trang
|
||||
const button = ".form-input-wrapper.form-group > .btn.btn-primary"; // Nút để tiếp tục quá trình xác minh
|
||||
const remember = ".PrivateSwitchBase-input"; // Checkbox "Remember me"
|
||||
const continueButton =
|
||||
".MuiButtonBase-root.MuiButton-root.MuiButton-contained.MuiButton-containedPrimary.MuiButton-sizeMedium.MuiButton-containedSizeMedium.MuiButton-colorPrimary.MuiButton-root"; // Nút "Continue"
|
||||
|
||||
// Chờ cho nút xác minh xuất hiện
|
||||
console.log(
|
||||
`🔎 [${this.id}] Waiting for the button with selector: ${button}`
|
||||
);
|
||||
await this.page_context.waitForSelector(button, { timeout: 8000 });
|
||||
|
||||
console.log(`✅ [${this.id}] Button found, clicking the first button.`);
|
||||
|
||||
// Lấy phần tử của nút và log nội dung của nó
|
||||
const firstButton = await this.page_context.$(button); // Lấy phần tử đầu tiên
|
||||
const buttonContent = await firstButton.evaluate((el) => el.textContent); // Lấy nội dung của nút
|
||||
console.log(`🔎 [${this.id}] Button content: ${buttonContent}`);
|
||||
|
||||
// Chờ 2s cho button sẵn sàn
|
||||
await delay(2000);
|
||||
// Click vào nút xác minh
|
||||
await firstButton.click();
|
||||
console.log(`✅ [${this.id}] Button clicked.`);
|
||||
|
||||
// Nhận mã OTP để nhập vào form
|
||||
const { name, code } = await this.waitVerifyData();
|
||||
console.log(
|
||||
`🔎 [${this.id}] Waiting for OTP input, received code: ${code}`
|
||||
);
|
||||
|
||||
// Nhập mã OTP vào form
|
||||
await this.enterOTP(code);
|
||||
console.log(`✅ [${this.id}] OTP entered successfully.`);
|
||||
|
||||
// Chờ cho checkbox "Remember me" xuất hiện
|
||||
await this.page_context.waitForSelector(remember, { timeout: 8000 });
|
||||
console.log(
|
||||
`🔎 [${this.id}] Waiting for remember me checkbox with selector: ${remember}`
|
||||
);
|
||||
|
||||
// Click vào checkbox "Remember me"
|
||||
await this.page_context.click(remember, { delay: 92 });
|
||||
console.log(`✅ [${this.id}] Remember me checkbox clicked.`);
|
||||
|
||||
// Chờ cho nút "Continue" xuất hiện
|
||||
await this.page_context.waitForSelector(continueButton, {
|
||||
timeout: 8000,
|
||||
});
|
||||
console.log(
|
||||
`🔎 [${this.id}] Waiting for continue button with selector: ${continueButton}`
|
||||
);
|
||||
|
||||
// Click vào nút "Continue"
|
||||
await this.page_context.click(continueButton, { delay: 100 });
|
||||
console.log(`✅ [${this.id}] Continue button clicked.`);
|
||||
|
||||
// Chờ cho trang tải hoàn tất sau khi click "Continue"
|
||||
await this.page_context.waitForNavigation({
|
||||
waitUntil: "domcontentloaded",
|
||||
});
|
||||
console.log(`✅ [${this.id}] Navigation completed.`);
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error(`❌ [${this.id}] Error:`, error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async handleLogin() {
|
||||
const page = this.page_context;
|
||||
|
||||
global.IS_CLEANING = false;
|
||||
|
||||
const filePath = getPathProfile(this.origin_url);
|
||||
|
||||
await page.waitForNavigation({ waitUntil: "domcontentloaded" });
|
||||
|
||||
// 🛠 Check if already logged in (login input should not be visible or profile exists)
|
||||
if (!(await page.$("#emailLogin")) && fs.existsSync(filePath)) {
|
||||
console.log(`✅ [${this.id}] Already logged in, skipping login process.`);
|
||||
return;
|
||||
}
|
||||
|
||||
waitVerifyData = async () =>
|
||||
new Promise((rev, rej) => {
|
||||
// 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 2 minute.`));
|
||||
}, 120 * 1000); // 60 giây
|
||||
if (fs.existsSync(filePath)) {
|
||||
console.log(`🗑 [${this.id}] Deleting existing file: ${filePath}`);
|
||||
fs.unlinkSync(filePath);
|
||||
}
|
||||
|
||||
global.socket.on(`verify-code.${this.origin_url}`, async (data) => {
|
||||
console.log(`📢 [${this.id}] VERIFY CODE:`, data);
|
||||
clearTimeout(timeout); // Hủy timeout vì đã nhận được mã
|
||||
global.socket.off(`verify-code.${this.origin_url}`); // Xóa listener tránh lặp lại
|
||||
rev(data); // Resolve với dữ liệu nhận được
|
||||
});
|
||||
const children = this.children.filter((item) => item.page_context);
|
||||
console.log(
|
||||
`🔍 [${this.id}] Found ${children.length} child pages to close.`
|
||||
);
|
||||
|
||||
if (children.length > 0) {
|
||||
console.log(`🛑 [${this.id}] Closing child pages...`);
|
||||
await Promise.all(
|
||||
children.map((item) => {
|
||||
console.log(
|
||||
`➡ [${this.id}] Closing child page with context: ${item.page_context}`
|
||||
);
|
||||
return safeClosePage(item);
|
||||
})
|
||||
);
|
||||
|
||||
console.log(
|
||||
`➡ [${this.id}] Closing main page context: ${this.page_context}`
|
||||
);
|
||||
await safeClosePage(this);
|
||||
}
|
||||
|
||||
console.log(`🔑 [${this.id}] Starting login process...`);
|
||||
|
||||
try {
|
||||
// ⌨ Enter email
|
||||
console.log(`✍ [${this.id}] Entering email:`, this.username);
|
||||
await page.type("#emailLogin", this.username, { delay: 100 });
|
||||
|
||||
// ⌨ Enter password
|
||||
console.log(`✍ [${this.id}] Entering password...`);
|
||||
await page.type("#passwordLogin", this.password, { delay: 150 });
|
||||
|
||||
// 🚀 Click the login button
|
||||
console.log(`🔘 [${this.id}] Clicking the "Login" button`);
|
||||
await page.click("#signInBtn", { delay: 92 });
|
||||
|
||||
const result = await this.waitToTwoVerify();
|
||||
|
||||
// ⏳ Wait for navigation after login
|
||||
if (!result) {
|
||||
console.log(`⏳ [${this.id}] Waiting for navigation after login...`);
|
||||
await page.waitForNavigation({
|
||||
timeout: 8000,
|
||||
waitUntil: "domcontentloaded",
|
||||
});
|
||||
}
|
||||
|
||||
async isLogin() {
|
||||
if (!this.page_context) return false;
|
||||
|
||||
const filePath = getPathProfile(this.origin_url);
|
||||
|
||||
return !(await this.page_context.$('#emailLogin')) && fs.existsSync(filePath);
|
||||
if (this.page_context.url() == this.url) {
|
||||
// 📂 Save session context to avoid re-login
|
||||
await this.saveContext();
|
||||
console.log(`✅ [${this.id}] Login successful!`);
|
||||
} else {
|
||||
console.log(`❌ [${this.id}] Login Failure!`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`❌ [${this.id}] Error during login process:`,
|
||||
error.message
|
||||
);
|
||||
} finally {
|
||||
global.IS_CLEANING = true;
|
||||
}
|
||||
}
|
||||
|
||||
waitVerifyData = async () =>
|
||||
new Promise((rev, rej) => {
|
||||
// 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
|
||||
action = async () => {
|
||||
try {
|
||||
const page = this.page_context;
|
||||
|
||||
global.socket.on(`verify-code.${this.origin_url}`, async (data) => {
|
||||
console.log(`📢 [${this.id}] VERIFY CODE:`, data);
|
||||
clearTimeout(timeout); // Hủy timeout vì đã nhận được mã
|
||||
global.socket.off(`verify-code.${this.origin_url}`); // Xóa listener tránh lặp lại
|
||||
rev(data); // Resolve với dữ liệu nhận được
|
||||
});
|
||||
});
|
||||
|
||||
async enterOTP(otp) {
|
||||
try {
|
||||
// Selector cho tất cả các input OTP
|
||||
const inputSelector = '.MuiDialog-container .container input';
|
||||
|
||||
// Chờ cho các input OTP xuất hiện
|
||||
await this.page_context.waitForSelector(inputSelector, { timeout: 8000 });
|
||||
|
||||
// Lấy tất cả các input OTP
|
||||
const inputs = await this.page_context.$$(inputSelector);
|
||||
|
||||
// Kiểm tra nếu có đúng 6 trường input
|
||||
if (inputs.length === 6 && otp.length === 6) {
|
||||
// Nhập mỗi ký tự của OTP vào các input tương ứng
|
||||
for (let i = 0; i < 6; i++) {
|
||||
await inputs[i].type(otp[i], { delay: 100 });
|
||||
}
|
||||
console.log(`✅ OTP entered successfully: ${otp}`);
|
||||
} else {
|
||||
console.error('❌ Invalid OTP or input fields count');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ Error entering OTP:', error);
|
||||
page.on("response", async (response) => {
|
||||
const request = response.request();
|
||||
if (request.redirectChain().length > 0) {
|
||||
if (response.url().includes(configs.WEB_CONFIGS.LAWSONS.LOGIN_URL)) {
|
||||
await this.handleLogin();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
await page.goto(this.url, { waitUntil: "networkidle2" });
|
||||
|
||||
await page.bringToFront();
|
||||
|
||||
// Set userAgent
|
||||
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"
|
||||
);
|
||||
} catch (error) {
|
||||
console.log("Error [action]: ", error.message);
|
||||
}
|
||||
};
|
||||
|
||||
async waitToTwoVerify() {
|
||||
try {
|
||||
if (!this.page_context) return false;
|
||||
listen_events = async () => {
|
||||
if (this.page_context) return;
|
||||
|
||||
// Selector của các phần tử trên trang
|
||||
const button = '.form-input-wrapper.form-group > .btn.btn-primary'; // Nút để tiếp tục quá trình xác minh
|
||||
const remember = '.PrivateSwitchBase-input'; // Checkbox "Remember me"
|
||||
const continueButton =
|
||||
'.MuiButtonBase-root.MuiButton-root.MuiButton-contained.MuiButton-containedPrimary.MuiButton-sizeMedium.MuiButton-containedSizeMedium.MuiButton-colorPrimary.MuiButton-root'; // Nút "Continue"
|
||||
// await this.puppeteer_connect();
|
||||
// await this.action();
|
||||
|
||||
// Chờ cho nút xác minh xuất hiện
|
||||
console.log(`🔎 [${this.id}] Waiting for the button with selector: ${button}`);
|
||||
await this.page_context.waitForSelector(button, { timeout: 8000 });
|
||||
const results = await this.handlePrevListen();
|
||||
|
||||
console.log(`✅ [${this.id}] Button found, clicking the first button.`);
|
||||
if (!results) return;
|
||||
|
||||
// Lấy phần tử của nút và log nội dung của nó
|
||||
const firstButton = await this.page_context.$(button); // Lấy phần tử đầu tiên
|
||||
const buttonContent = await firstButton.evaluate((el) => el.textContent); // Lấy nội dung của nút
|
||||
console.log(`🔎 [${this.id}] Button content: ${buttonContent}`);
|
||||
|
||||
// Chờ 2s cho button sẵn sàn
|
||||
await delay(2000);
|
||||
// Click vào nút xác minh
|
||||
await firstButton.click();
|
||||
console.log(`✅ [${this.id}] Button clicked.`);
|
||||
|
||||
// Nhận mã OTP để nhập vào form
|
||||
const { name, code } = await this.waitVerifyData();
|
||||
console.log(`🔎 [${this.id}] Waiting for OTP input, received code: ${code}`);
|
||||
|
||||
// Nhập mã OTP vào form
|
||||
await this.enterOTP(code);
|
||||
console.log(`✅ [${this.id}] OTP entered successfully.`);
|
||||
|
||||
// Chờ cho checkbox "Remember me" xuất hiện
|
||||
await this.page_context.waitForSelector(remember, { timeout: 8000 });
|
||||
console.log(`🔎 [${this.id}] Waiting for remember me checkbox with selector: ${remember}`);
|
||||
|
||||
// Click vào checkbox "Remember me"
|
||||
await this.page_context.click(remember, { delay: 92 });
|
||||
console.log(`✅ [${this.id}] Remember me checkbox clicked.`);
|
||||
|
||||
// Chờ cho nút "Continue" xuất hiện
|
||||
await this.page_context.waitForSelector(continueButton, { timeout: 8000 });
|
||||
console.log(`🔎 [${this.id}] Waiting for continue button with selector: ${continueButton}`);
|
||||
|
||||
// Click vào nút "Continue"
|
||||
await this.page_context.click(continueButton, { delay: 100 });
|
||||
console.log(`✅ [${this.id}] Continue button clicked.`);
|
||||
|
||||
// Chờ cho trang tải hoàn tất sau khi click "Continue"
|
||||
await this.page_context.waitForNavigation({ waitUntil: 'domcontentloaded' });
|
||||
console.log(`✅ [${this.id}] Navigation completed.`);
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error(`❌ [${this.id}] Error:`, error);
|
||||
return false;
|
||||
this.reloadInterval = setInterval(async () => {
|
||||
try {
|
||||
if (this.page_context && !this.page_context.isClosed()) {
|
||||
console.log(`🔄 [${this.id}] Reloading page...`);
|
||||
await this.page_context.reload({ waitUntil: "networkidle2" });
|
||||
console.log(`✅ [${this.id}] Page reloaded successfully.`);
|
||||
} else {
|
||||
console.log(
|
||||
`❌ [${this.id}] Page context is closed. Stopping reload.`
|
||||
);
|
||||
clearInterval(this.reloadInterval);
|
||||
}
|
||||
}
|
||||
|
||||
async handleLogin() {
|
||||
const page = this.page_context;
|
||||
|
||||
global.IS_CLEANING = false;
|
||||
|
||||
const filePath = getPathProfile(this.origin_url);
|
||||
|
||||
await page.waitForNavigation({ waitUntil: 'domcontentloaded' });
|
||||
|
||||
// 🛠 Check if already logged in (login input should not be visible or profile exists)
|
||||
if (!(await page.$('#emailLogin')) && fs.existsSync(filePath)) {
|
||||
console.log(`✅ [${this.id}] Already logged in, skipping login process.`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (fs.existsSync(filePath)) {
|
||||
console.log(`🗑 [${this.id}] Deleting existing file: ${filePath}`);
|
||||
fs.unlinkSync(filePath);
|
||||
}
|
||||
|
||||
const children = this.children.filter((item) => item.page_context);
|
||||
console.log(`🔍 [${this.id}] Found ${children.length} child pages to close.`);
|
||||
|
||||
if (children.length > 0) {
|
||||
console.log(`🛑 [${this.id}] Closing child pages...`);
|
||||
await Promise.all(
|
||||
children.map((item) => {
|
||||
console.log(`➡ [${this.id}] Closing child page with context: ${item.page_context}`);
|
||||
return safeClosePage(item);
|
||||
}),
|
||||
);
|
||||
|
||||
console.log(`➡ [${this.id}] Closing main page context: ${this.page_context}`);
|
||||
await safeClosePage(this);
|
||||
}
|
||||
|
||||
console.log(`🔑 [${this.id}] Starting login process...`);
|
||||
|
||||
try {
|
||||
// ⌨ Enter email
|
||||
console.log(`✍ [${this.id}] Entering email:`, this.username);
|
||||
await page.type('#emailLogin', this.username, { delay: 100 });
|
||||
|
||||
// ⌨ Enter password
|
||||
console.log(`✍ [${this.id}] Entering password...`);
|
||||
await page.type('#passwordLogin', this.password, { delay: 150 });
|
||||
|
||||
// 🚀 Click the login button
|
||||
console.log(`🔘 [${this.id}] Clicking the "Login" button`);
|
||||
await page.click('#signInBtn', { delay: 92 });
|
||||
|
||||
const result = await this.waitToTwoVerify();
|
||||
|
||||
// ⏳ Wait for navigation after login
|
||||
if (!result) {
|
||||
console.log(`⏳ [${this.id}] Waiting for navigation after login...`);
|
||||
await page.waitForNavigation({ timeout: 8000, waitUntil: 'domcontentloaded' });
|
||||
}
|
||||
|
||||
if (this.page_context.url() == this.url) {
|
||||
// 📂 Save session context to avoid re-login
|
||||
await this.saveContext();
|
||||
console.log(`✅ [${this.id}] Login successful!`);
|
||||
} else {
|
||||
console.log(`❌ [${this.id}] Login Failure!`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`❌ [${this.id}] Error during login process:`, error.message);
|
||||
} finally {
|
||||
global.IS_CLEANING = true;
|
||||
}
|
||||
}
|
||||
|
||||
action = async () => {
|
||||
try {
|
||||
const page = this.page_context;
|
||||
|
||||
page.on('response', async (response) => {
|
||||
const request = response.request();
|
||||
if (request.redirectChain().length > 0) {
|
||||
if (response.url().includes(configs.WEB_CONFIGS.LAWSONS.LOGIN_URL)) {
|
||||
await this.handleLogin();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
await page.goto(this.url, { waitUntil: 'networkidle2' });
|
||||
|
||||
await page.bringToFront();
|
||||
|
||||
// Set userAgent
|
||||
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');
|
||||
} catch (error) {
|
||||
console.log('Error [action]: ', error.message);
|
||||
}
|
||||
};
|
||||
|
||||
listen_events = async () => {
|
||||
if (this.page_context) return;
|
||||
|
||||
await this.puppeteer_connect();
|
||||
await this.action();
|
||||
|
||||
this.reloadInterval = setInterval(async () => {
|
||||
try {
|
||||
if (this.page_context && !this.page_context.isClosed()) {
|
||||
console.log(`🔄 [${this.id}] Reloading page...`);
|
||||
await this.page_context.reload({ waitUntil: 'networkidle2' });
|
||||
console.log(`✅ [${this.id}] Page reloaded successfully.`);
|
||||
} else {
|
||||
console.log(`❌ [${this.id}] Page context is closed. Stopping reload.`);
|
||||
clearInterval(this.reloadInterval);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`🚨 [${this.id}] Error reloading page:`, error.message);
|
||||
}
|
||||
}, 60000); // 1p reload
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(`🚨 [${this.id}] Error reloading page:`, error.message);
|
||||
}
|
||||
}, 60000); // 1p reload
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -128,8 +128,12 @@ export class PicklesApiBid extends ApiBid {
|
|||
listen_events = async () => {
|
||||
if (this.page_context) return;
|
||||
|
||||
await this.puppeteer_connect();
|
||||
await this.action();
|
||||
// await this.puppeteer_connect();
|
||||
// await this.action();
|
||||
|
||||
const results = await this.handlePrevListen();
|
||||
|
||||
if (!results) return;
|
||||
|
||||
this.reloadInterval = setInterval(async () => {
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import CONSTANTS from "./constants.js";
|
|||
import fs from "fs";
|
||||
import path from "path";
|
||||
import { updateStatusWork } from "./apis/bid.js";
|
||||
import _ from "lodash";
|
||||
|
||||
export const isNumber = (value) => !isNaN(value) && !isNaN(parseFloat(value));
|
||||
|
||||
|
|
@ -303,3 +304,21 @@ export async function isPageAvailable(page) {
|
|||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function findNearestClosingChild(webBid) {
|
||||
const now = Date.now();
|
||||
|
||||
const validChildren = webBid.children.filter(
|
||||
(child) => child.close_time && !isNaN(new Date(child.close_time).getTime())
|
||||
);
|
||||
|
||||
if (validChildren.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const nearestChild = _.minBy(validChildren, (child) => {
|
||||
return Math.abs(new Date(child.close_time).getTime() - now);
|
||||
});
|
||||
|
||||
return nearestChild || null;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue