181 lines
		
	
	
		
			6.3 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
			
		
		
	
	
			181 lines
		
	
	
		
			6.3 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 async function safeClosePage(item) {
 | 
						|
    try {
 | 
						|
        const page = item.page_context;
 | 
						|
 | 
						|
        if (!page?.isClosed() && page?.close) {
 | 
						|
            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);
 | 
						|
}
 |