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 && !page.isClosed() && page.close) { await safeClosePageReal(page); } if (item?.page_context) { item.page_context = undefined; } if (item?.browser_context) { try { await item.browser_context.close(); } catch (ctxErr) { console.warn( `⚠️ Failed to close browser context for item ${item.id}: ${ctxErr.message}` ); } item.browser_context = undefined; } } catch (error) { console.warn(`⚠️ Can't close item ${item?.id}: ${error.message}`); } } 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 const getPathLocalData = (origin_url) => { return path.join( CONSTANTS.LOCAL_DATA_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 ` ⭉ Activate Auto Bid
📌 Product: ${data.name}
🔗 Link: Click here
💰 Max Price: $${data.max_price}
🌐 Platform: Langtons `; }; 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(); } export function subtractMinutes(time, minutes) { const date = new Date(time); date.setMinutes(date.getMinutes() - minutes); return date.toUTCString(); } export function subtractSeconds(time, seconds) { const date = new Date(time); date.setSeconds(date.getSeconds() - seconds); return date.toUTCString(); } export async function isPageAvailable(page) { if (!page || page.isClosed()) return false; try { await Promise.race([ page.title(), // hoặc page.url(), evaluate, vv new Promise((_, reject) => setTimeout(() => reject(new Error("Timeout")), 1000) ), ]); return true; } catch (err) { console.warn(`⚠️ Page not available: ${err.message}`); return false; } }