218 lines
6.3 KiB
TypeScript
218 lines
6.3 KiB
TypeScript
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));
|