import { Bid } from '@/modules/bids/entities/bid.entity'; import * as moment from 'moment'; export function extractModelId(url: string): string | null { switch (extractDomain(url)) { case 'https://www.grays.com': { const match = url.match(/\/lot\/([\d-]+)\//); return match ? match[1] : null; } case 'https://www.langtons.com.au': { const match = url.match(/auc-var-\d+/); return match[0]; } case 'https://www.lawsons.com.au': { const match = url.split('_'); return match ? match[1] : null; } case 'https://www.pickles.com.au': { const model = url.split('/').pop(); return model ? model : null; } case 'https://www.allbids.com.au': { const match = url.match(/-(\d+)(?:[\?#]|$)/); return match ? match[1] : null; } } } export function subtractMinutes(timeStr: string, minutes: number) { const date = new Date(timeStr); // Chuyển chuỗi thành đối tượng Date date.setMinutes(date.getMinutes() - minutes); // Trừ đi số phút return date.toISOString(); // Trả về dạng chuẩn ISO } export function extractDomainSmart(url: string) { const PUBLIC_SUFFIXES = ['com.au', 'co.uk', 'com.vn', 'org.au', 'gov.uk']; try { const hostname = new URL(url).hostname.replace(/^www\./, ''); // remove "www." const parts = hostname.split('.'); for (let i = 0; i < PUBLIC_SUFFIXES.length; i++) { if (hostname.endsWith(PUBLIC_SUFFIXES[i])) { return parts[parts.length - PUBLIC_SUFFIXES[i].split('.').length - 1]; } } return parts[parts.length - 2]; } catch (e) { return url; } } 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(); return now >= targetDate; } export function extractDomain(url: string): string | null { try { const parsedUrl = new URL(url); return parsedUrl.origin; } catch (error) { return null; } } export function escapeMarkdownV2(text: string) { return text.replace(/[_*[\]()~`>#+\-=|{}.!\\]/g, '\\$&'); } export function extractVerifyCodeLANGTONS(content: string) { const match = content.match(/Your\s+(\w+)\s+verific.*?code\s+is:\s+(\d+)/i); if (match) { const name = match[1]; // LANGTONS const code = match[2]; // 012152 return { name, code, }; } else { return null; } } export function extractVerifyCodeLAWSONS(content: string) { if (!content) return null; // Loại bỏ các ký tự `=` không cần thiết do email encoding (quoted-printable) content = content.replace(/=\r\n/g, '').replace(/=3D/g, '='); // Regex để tìm mã xác minh (6 chữ số) const codeMatch = content.match(/Verification code:\s*(\d{6})/); const code = codeMatch ? codeMatch[1] : null; // Regex để tìm tên (trong đoạn "Lawsons | Sydney, AU") const nameMatch = content.match(/([\w\s]+)\s*\|\s*[\w\s,]+/); const name = nameMatch ? nameMatch[1].trim() : null; // Nếu không tìm thấy cả hai giá trị, trả về null if (!code && !name) { return null; } return { code, name }; } export function verifyCode(content: string) { // Kiểm tra mã xác minh từ LANGTONS const langtonsResult = extractVerifyCodeLANGTONS(content); if (langtonsResult) { return langtonsResult; } // Nếu không có kết quả, tiếp tục kiểm tra mã xác minh từ LAWSONS const lawsonsResult = extractVerifyCodeLAWSONS(content); if (lawsonsResult) { return lawsonsResult; // Trả về kết quả đúng thay vì langtonsResult } // Nếu không tìm thấy mã xác minh, trả về null return null; } export function shouldResetTool( bids: Bid[], lastResetTime: Date | null, now: Date = new Date(), ) { const ONE_MINUTE = 60 * 1000; const ONE_HOUR = 60 * ONE_MINUTE; const TWO_HOURS = 2 * ONE_HOUR; const TWENTY_MINUTES = 20 * ONE_MINUTE; const FIVE_MINUTES = 5 * ONE_MINUTE; // Nếu đã reset trong 1 giờ gần đây => không reset if (lastResetTime && now.getTime() - lastResetTime.getTime() < ONE_HOUR) { return { shouldReset: false, }; } // 1. Kiểm tra bid gần nhất có close_time trong vòng 20 phút tới const futureBids = bids .filter((b) => b.close_time) .map((b) => ({ ...b, closeTime: new Date(b.close_time!), })) .filter((b) => b.closeTime.getTime() > now.getTime()) .sort((a, b) => a.closeTime.getTime() - b.closeTime.getTime()); const closest = futureBids[0]; const hasBidCloseSoon = closest && closest.closeTime.getTime() - now.getTime() <= TWENTY_MINUTES; if (hasBidCloseSoon) { return { shouldReset: true, reason: 'Bid close_time is within 20 minutes', bidId: closest.id, closeTime: closest.close_time, }; } // 2. Kiểm tra bid chưa có lot_id hoặc close_time, được tạo > 5 phút và cách reset trước > 2 tiếng // Đồng thời không có bid nào sắp close trong 20 phút tới if (!hasBidCloseSoon) { for (const bid of bids) { const createdAt = new Date(bid.created_at); if ( (!bid.lot_id || !bid.close_time) && now.getTime() - createdAt.getTime() > FIVE_MINUTES && (!lastResetTime || now.getTime() - lastResetTime.getTime() > TWO_HOURS) ) { return { shouldReset: true, reason: 'Bid is missing info and older than 5 mins, last reset > 2h, and no urgent bids', bidId: bid.id, closeTime: bid.close_time, }; } } } return { shouldReset: false, }; } export function extractNumber(str: string) { const match = str.match(/\d+(\.\d+)?/); return match ? parseFloat(match[0]) : null; } export function formatEndTime( closeTime: string | Date, extended: boolean, ): string { return `${moment(closeTime).format('YYYY-MM-DD HH:mm')} (${extended ? 'extended' : 'no extension'})`; } export const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));