bid-tool/auto-bid-tool/system/utils.js

261 lines
7.7 KiB
JavaScript

import CONSTANTS from "./constants.js";
import fs from "fs";
import path from "path";
import { updateStatusWork } from "./apis/bid.js";
export const isNumber = (value) => !isNaN(value) && !isNaN(parseFloat(value));
export const takeSnapshot = async (
page,
item,
imageName,
type = CONSTANTS.TYPE_IMAGE.ERRORS
) => {
if (!page || page.isClosed()) return;
try {
const baseDir = path.join(
CONSTANTS.ERROR_IMAGES_PATH,
item.type,
String(item.id)
); // Thư mục theo lot_id
const typeDir = path.join(baseDir, type); // Thư mục con theo type
// Tạo tên file, nếu type === 'work' thì không có timestamp
const fileName =
type === CONSTANTS.TYPE_IMAGE.WORK
? `${imageName}.png`
: `${imageName}_${new Date().toISOString().replace(/[:.]/g, "-")}.png`;
const filePath = path.join(typeDir, fileName);
// Kiểm tra và tạo thư mục nếu chưa tồn tại
if (!fs.existsSync(typeDir)) {
fs.mkdirSync(typeDir, { recursive: true });
console.log(`📂 Save at folder: ${typeDir}`);
}
// await page.waitForSelector('body', { visible: true, timeout: 5000 });
// Kiểm tra có thể điều hướng trang không
const isPageResponsive = await page.evaluate(
() => document.readyState === "complete"
);
if (!isPageResponsive) {
console.log("🚫 Page is unresponsive, skipping snapshot.");
return;
}
// Chờ tối đa 15 giây, nếu không thấy thì bỏ qua
await page
.waitForSelector("body", { visible: true, timeout: 15000 })
.catch(() => {
console.log("⚠️ Body selector not found, skipping snapshot.");
return;
});
// Chụp ảnh màn hình và lưu vào filePath
await page.screenshot({ path: filePath });
console.log(`📸 Image saved at: ${filePath}`);
// Nếu type === 'work', gửi ảnh lên API
if (type === CONSTANTS.TYPE_IMAGE.WORK) {
await updateStatusWork(item, filePath);
}
} catch (error) {
console.log("Error when snapshot: " + error.message);
}
};
export const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
export const safeClosePageReal = async (page) => {
if (!page) return;
try {
if (page.isClosed()) {
console.log(`✅ Page already closed: ${page.url()}`);
return;
}
page.removeAllListeners(); // ✂️ Remove hết listeners trước khi close
await page.close({ runBeforeUnload: true }); // 🛑 Đóng an toàn
console.log(`✅ Successfully closed page: ${page.url()}`);
} catch (err) {
console.warn(
`⚠️ Error closing page ${page.url ? page.url() : ""}: ${err.message}`
);
}
};
export async function safeClosePage(item) {
try {
const page = item.page_context;
if (!page?.isClosed() && page?.close) {
await safeClosePageReal(page);
// await page.close();
}
item.page_context = undefined;
if (item?.page_context) {
item.page_context = undefined;
}
} catch (error) {
console.log("Can't close item: " + item.id);
}
}
export function isTimeReached(targetTime) {
if (!targetTime) return false;
const targetDate = new Date(targetTime);
const now = new Date();
return now >= targetDate;
}
export function extractNumber(str) {
const match = str.match(/\d+(\.\d+)?/);
return match ? parseFloat(match[0]) : null;
}
export const sanitizeFileName = (url) => {
return url.replace(/[:\/]/g, "_");
};
export const getPathProfile = (origin_url) => {
return path.join(
CONSTANTS.PROFILE_PATH,
sanitizeFileName(origin_url) + ".json"
);
};
export function removeFalsyValues(obj, excludeKeys = []) {
return Object.entries(obj).reduce((acc, [key, value]) => {
if (value || excludeKeys.includes(key)) {
acc[key] = value;
}
return acc;
}, {});
}
export const enableAutoBidMessage = (data) => {
return `
<b>⭉ Activate Auto Bid</b><br>
📌 Product: <b>${data.name}</b><br>
🔗 Link: <a href="${data.url}">Click here</a><br>
💰 Max Price: <b>$${data.max_price}</b><br>
🌐 Platform: <a href="${data.web_bid.origin_url}">Langtons</a>
`;
};
export function convertAETtoUTC(dateString) {
// Bảng ánh xạ tên tháng sang số (0-11, theo chuẩn JavaScript)
const monthMap = {
Jan: 0,
Feb: 1,
Mar: 2,
Apr: 3,
May: 4,
Jun: 5,
Jul: 6,
Aug: 7,
Sep: 8,
Oct: 9,
Nov: 10,
Dec: 11,
};
// Tách chuỗi đầu vào
const parts = dateString.match(
/(\w+)\s(\d+)\s(\w+)\s(\d+),\s(\d+)\s(PM|AM)\sAET/
);
if (!parts) {
throw new Error("Error format: 'Sun 6 Apr 2025, 9 PM AET'");
}
const [, , day, month, year, hour, period] = parts;
// Chuyển đổi giờ sang định dạng 24h
let hours = parseInt(hour, 10);
if (period === "PM" && hours !== 12) hours += 12;
if (period === "AM" && hours === 12) hours = 0;
// Tạo đối tượng Date ban đầu (chưa điều chỉnh múi giờ)
const date = new Date(
Date.UTC(
parseInt(year, 10),
monthMap[month],
parseInt(day, 10),
hours,
0,
0
)
);
// Hàm kiểm tra DST cho AET
function isDST(date) {
const year = date.getUTCFullYear();
const month = date.getUTCMonth();
const day = date.getUTCDate();
// DST bắt đầu: Chủ nhật đầu tiên của tháng 10 (2:00 AM AEST -> 3:00 AM AEDT)
const dstStart = new Date(Date.UTC(year, 9, 1, 0, 0, 0)); // 1/10
dstStart.setUTCDate(1 + ((7 - dstStart.getUTCDay()) % 7)); // Chủ nhật đầu tiên
const dstStartTime = dstStart.getTime() + 2 * 60 * 60 * 1000; // 2:00 AM UTC+10
// DST kết thúc: Chủ nhật đầu tiên của tháng 4 (3:00 AM AEDT -> 2:00 AM AEST)
const dstEnd = new Date(Date.UTC(year, 3, 1, 0, 0, 0)); // 1/4
dstEnd.setUTCDate(1 + ((7 - dstEnd.getUTCDay()) % 7)); // Chủ nhật đầu tiên
const dstEndTime = dstEnd.getTime() + 3 * 60 * 60 * 1000; // 3:00 AM UTC+11
const currentTime = date.getTime() + 10 * 60 * 60 * 1000; // Thời gian AET (giả định ban đầu UTC+10)
return currentTime >= dstStartTime && currentTime < dstEndTime;
}
// Xác định offset dựa trên DST
const offset = isDST(date) ? 11 : 10; // UTC+11 nếu DST, UTC+10 nếu không
// Điều chỉnh thời gian về UTC
const utcDate = new Date(date.getTime() - offset * 60 * 60 * 1000);
// Trả về chuỗi UTC
return utcDate.toUTCString();
}
export function extractPriceNumber(priceString) {
const cleaned = priceString.replace(/[^\d.]/g, "");
return parseFloat(cleaned);
}
export function findEarlyLoginTime(webBid) {
const now = new Date();
// Bước 1: Lọc ra những bid có close_time hợp lệ
const validChildren = webBid.children.filter((child) => child.close_time);
if (validChildren.length === 0) return null;
// Bước 2: Tìm bid có close_time gần hiện tại nhất
const closestBid = validChildren.reduce((closest, current) => {
const closestDiff = Math.abs(
new Date(closest.close_time).getTime() - now.getTime()
);
const currentDiff = Math.abs(
new Date(current.close_time).getTime() - now.getTime()
);
return currentDiff < closestDiff ? current : closest;
});
if (!closestBid.close_time) return null;
// Bước 3: Tính toán thời gian login sớm
const closeTime = new Date(closestBid.close_time);
closeTime.setSeconds(
closeTime.getSeconds() - (webBid.early_login_seconds || 0)
);
return closeTime.toISOString();
}