Merge pull request 'update clear lazy tab' (#23) from zelda.by-pass-langtons-prev-code into main
Reviewed-on: #23
This commit is contained in:
commit
838e025274
|
|
@ -6,7 +6,7 @@ import { Socket } from "socket.io-client";
|
|||
import { getImagesWorking } from "../../apis/bid";
|
||||
import { useStatusToolStore } from "../../lib/zustand/use-status-tool-store";
|
||||
import { IBid, IWebBid } from "../../system/type";
|
||||
import { cn, stringToColor } from "../../utils";
|
||||
import { cn, extractDomainSmart, stringToColor } from "../../utils";
|
||||
import ShowImageModal from "./show-image-modal";
|
||||
import { isTimeReached, subtractSeconds } from "../../lib/table/ultils";
|
||||
export interface IWorkingPageProps {
|
||||
|
|
@ -129,7 +129,7 @@ export default function WorkingPage({ data, socket }: IWorkingPageProps) {
|
|||
src={imageSrc}
|
||||
/>
|
||||
|
||||
<Box className="absolute bg-black/60 inset-0 flex items-center justify-center text-white font-bold flex-col gap-3 p-4 rounded-lg transition duration-300 hover:bg-opacity-70">
|
||||
<Box className="absolute bg-black/60 inset-0 flex items-center justify-center text-white font-bold flex-col gap-2 p-4 rounded-lg transition duration-300 hover:bg-opacity-70">
|
||||
<Text className="text-lg tracking-wide text-center font-bold">
|
||||
{isIBid(data) ? data.name : "Tracking page"}
|
||||
</Text>
|
||||
|
|
@ -207,11 +207,11 @@ export default function WorkingPage({ data, socket }: IWorkingPageProps) {
|
|||
|
||||
<Badge
|
||||
color={stringToColor(
|
||||
isIBid(data) ? data.web_bid.origin_url : data.origin_url
|
||||
isIBid(data) ? extractDomainSmart(data.web_bid.origin_url) : extractDomainSmart(data.origin_url)
|
||||
)}
|
||||
size="xs"
|
||||
>
|
||||
{isIBid(data) ? data.web_bid.origin_url : data.origin_url}
|
||||
{isIBid(data) ? extractDomainSmart(data.web_bid.origin_url) : extractDomainSmart(data.origin_url)}
|
||||
</Badge>
|
||||
</Box>
|
||||
</Box>
|
||||
|
|
|
|||
|
|
@ -18,13 +18,13 @@ import {
|
|||
ShowHistoriesBidPicklesApiModal,
|
||||
ShowHistoriesModal,
|
||||
} from "../components/bid";
|
||||
import constants, { haveHistories } from "../constant";
|
||||
import Table from "../lib/table/table";
|
||||
import { IColumn, TRefTableFn } from "../lib/table/type";
|
||||
import { useConfirmStore } from "../lib/zustand/use-confirm";
|
||||
import { mappingStatusColors } from "../system/constants";
|
||||
import { IBid } from "../system/type";
|
||||
import { formatTime } from "../utils";
|
||||
import constants, { haveHistories } from "../constant";
|
||||
import { extractDomainSmart, formatTime } from "../utils";
|
||||
|
||||
export default function Bids() {
|
||||
const refTableFn: TRefTableFn<IBid> = useRef({});
|
||||
|
|
@ -57,7 +57,7 @@ export default function Bids() {
|
|||
title: "Web",
|
||||
typeFilter: "text",
|
||||
renderRow(row) {
|
||||
return <span>{row.web_bid.origin_url}</span>;
|
||||
return <span>{extractDomainSmart(row.web_bid.origin_url)}</span>;
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -157,14 +157,18 @@ export function findEarlyLoginTime(webBid: IWebBid): string | null {
|
|||
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);
|
||||
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());
|
||||
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;
|
||||
});
|
||||
|
||||
|
|
@ -172,7 +176,28 @@ export function findEarlyLoginTime(webBid: IWebBid): string | 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_tracking_seconds || 0));
|
||||
closeTime.setSeconds(
|
||||
closeTime.getSeconds() - (webBid.early_tracking_seconds || 0)
|
||||
);
|
||||
|
||||
return closeTime.toISOString();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
/public
|
||||
/system/error-images
|
||||
/system/profiles
|
||||
/system/local-data
|
||||
|
||||
# Logs
|
||||
logs
|
||||
|
|
|
|||
|
|
@ -269,6 +269,20 @@ const clearLazyTab = async () => {
|
|||
// product tabs
|
||||
const productTabs = _.flatMap(MANAGER_BIDS, "children");
|
||||
|
||||
for (const item of [...productTabs, ...MANAGER_BIDS]) {
|
||||
if (!item.page_context) continue;
|
||||
|
||||
try {
|
||||
if (!(await item.page_context.title())) {
|
||||
// await vì title() là async
|
||||
await safeClosePage(item);
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn("⚠️ Error checking page_context.title()", e.message);
|
||||
await safeClosePage(item);
|
||||
}
|
||||
}
|
||||
|
||||
for (const page of pages) {
|
||||
try {
|
||||
if (page.isClosed()) continue; // Trang đã đóng thì bỏ qua
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import browser from "../system/browser.js";
|
|||
import CONSTANTS from "../system/constants.js";
|
||||
import {
|
||||
findEarlyLoginTime,
|
||||
getPathLocalData,
|
||||
getPathProfile,
|
||||
isTimeReached,
|
||||
sanitizeFileName,
|
||||
|
|
@ -141,4 +142,102 @@ export class ApiBid extends Bid {
|
|||
|
||||
return earlyLoginTime && isTimeReached(earlyLoginTime);
|
||||
}
|
||||
|
||||
async saveCodeToLocal({ name, code }) {
|
||||
try {
|
||||
const filePath = getPathLocalData(this.origin_url); // file path
|
||||
const dirPath = path.dirname(filePath); // lấy thư mục cha
|
||||
|
||||
// kiểm tra folder chứa file đã tồn tại chưa
|
||||
if (!fs.existsSync(dirPath)) {
|
||||
fs.mkdirSync(dirPath, { recursive: true });
|
||||
}
|
||||
|
||||
// ghi file
|
||||
fs.writeFileSync(
|
||||
filePath,
|
||||
JSON.stringify({ name, code, time: Date.now() }, null, 2) // format JSON đẹp
|
||||
);
|
||||
} catch (error) {
|
||||
console.log(
|
||||
`%cerror [${this.id}] models/api-bid.js line:149`,
|
||||
"color: red; display: block; width: 100%;",
|
||||
error
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async clearCodeFromLocal() {
|
||||
try {
|
||||
const filePath = getPathLocalData(this.origin_url); // file path
|
||||
const dirPath = path.dirname(filePath); // lấy thư mục cha
|
||||
|
||||
// kiểm tra folder chứa file đã tồn tại chưa
|
||||
if (!fs.existsSync(dirPath)) {
|
||||
fs.mkdirSync(dirPath, { recursive: true });
|
||||
}
|
||||
|
||||
// ghi file
|
||||
fs.writeFileSync(
|
||||
filePath,
|
||||
JSON.stringify({}, null, 2) // format JSON đẹp
|
||||
);
|
||||
} catch (error) {
|
||||
console.log(
|
||||
`%cerror [${this.id}] models/api-bid.js line:187`,
|
||||
"color: red; display: block; width: 100%;",
|
||||
error
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async loadCodeFromLocal() {
|
||||
try {
|
||||
const filePath = getPathLocalData(this.origin_url);
|
||||
|
||||
if (!fs.existsSync(filePath)) {
|
||||
console.warn(
|
||||
`%cwarn [${this.id}] models/api-bid.js`,
|
||||
"color: orange; display: block; width: 100%;",
|
||||
`File not found: ${filePath}`
|
||||
);
|
||||
return null; // hoặc {} tùy bạn muốn trả gì
|
||||
}
|
||||
|
||||
const fileContent = fs.readFileSync(filePath, "utf-8");
|
||||
const data = JSON.parse(fileContent);
|
||||
|
||||
return data; // { name, code, time }
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`%cerror [${this.id}] models/api-bid.js`,
|
||||
"color: red; display: block; width: 100%;",
|
||||
error
|
||||
);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async isCodeValid(minutes = 8) {
|
||||
try {
|
||||
const data = await this.loadCodeFromLocal();
|
||||
if (!data || !data.time) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const now = Date.now();
|
||||
const timeDiff = now - data.time; // tính chênh lệch thời gian (ms)
|
||||
|
||||
const validDuration = minutes * 60 * 1000; // phút -> mili giây
|
||||
|
||||
return timeDiff <= validDuration;
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`%cerror [${this.id}] models/api-bid.js`,
|
||||
"color: red; display: block; width: 100%;",
|
||||
error
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
import fs from "fs";
|
||||
import configs from "../../system/config.js";
|
||||
import { getPathProfile, safeClosePage } from "../../system/utils.js";
|
||||
import {
|
||||
getPathLocalData,
|
||||
getPathProfile,
|
||||
safeClosePage,
|
||||
} from "../../system/utils.js";
|
||||
import { ApiBid } from "../api-bid.js";
|
||||
import _ from "lodash";
|
||||
import { updateStatusByPrice } from "../../system/apis/bid.js";
|
||||
|
|
@ -42,6 +46,169 @@ export class LangtonsApiBid extends ApiBid {
|
|||
);
|
||||
};
|
||||
|
||||
async callSubmitPrevCodeApi(code, csrfToken) {
|
||||
if (!this.page_context) return;
|
||||
try {
|
||||
const result = await this.page_context.evaluate(
|
||||
async (code, csrfToken) => {
|
||||
const formData = new FormData();
|
||||
formData.append("dwfrm_profile_login_code", code);
|
||||
formData.append("csrf_token", csrfToken);
|
||||
|
||||
try {
|
||||
const response = await fetch(
|
||||
"https://www.langtons.com.au/on/demandware.store/Sites-langtons-Site/en_AU/Login-VerifyOtpForLogin",
|
||||
{
|
||||
method: "POST",
|
||||
body: formData,
|
||||
}
|
||||
);
|
||||
|
||||
const data = await response.json();
|
||||
return { success: true, data };
|
||||
} catch (error) {
|
||||
return { success: false, error: error.toString() };
|
||||
}
|
||||
},
|
||||
code,
|
||||
csrfToken
|
||||
); // truyền biến vào page context
|
||||
|
||||
if (result.success) {
|
||||
console.log(`[${this.id}] callInsideApi API response:`, result.data);
|
||||
return result.data;
|
||||
} else {
|
||||
console.error(`[${this.id}] callInsideApi API error:`, result.error);
|
||||
return null;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`[${this.id}] Puppeteer evaluate error:`, error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async callSubmitAccountApi(csrfToken) {
|
||||
if (!this.page_context) return;
|
||||
try {
|
||||
const result = await this.page_context.evaluate(
|
||||
async ({ username, password }, csrfToken) => {
|
||||
const formData = new FormData();
|
||||
formData.append("loginEmail", username);
|
||||
formData.append("loginPassword", password);
|
||||
formData.append("rememberMe", true);
|
||||
formData.append("csrf_token", csrfToken);
|
||||
|
||||
try {
|
||||
const response = await fetch(
|
||||
"https://www.langtons.com.au/on/demandware.store/Sites-langtons-Site/en_AU/Account-Login?rurl=5",
|
||||
{
|
||||
method: "POST",
|
||||
body: formData,
|
||||
}
|
||||
);
|
||||
|
||||
const data = await response.json();
|
||||
return { success: true, data };
|
||||
} catch (error) {
|
||||
return { success: false, error: error.toString() };
|
||||
}
|
||||
},
|
||||
{ usernam: this.username, password: this.password },
|
||||
csrfToken
|
||||
); // truyền biến vào page context
|
||||
|
||||
if (result.success) {
|
||||
console.log(
|
||||
`[${this.id}] callSubmitAccountApi API response:`,
|
||||
result.data
|
||||
);
|
||||
return result.data;
|
||||
} else {
|
||||
console.error(
|
||||
`[${this.id}] callSubmitAccountApi API error:`,
|
||||
result.error
|
||||
);
|
||||
return null;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`[${this.id}] Puppeteer evaluate error:`, error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async getCsrfToken() {
|
||||
try {
|
||||
const csrfToken = await this.page_context.evaluate(() => {
|
||||
const csrfInput = document.querySelector(
|
||||
'input[name*="csrf"], input[name="_token"]'
|
||||
);
|
||||
return csrfInput ? csrfInput.value : null;
|
||||
});
|
||||
|
||||
if (csrfToken) {
|
||||
console.log(`✅ [${this.id}] CSRF token: ${csrfToken}`);
|
||||
return csrfToken;
|
||||
} else {
|
||||
console.warn(`⚠️ [${this.id}] No CSRF token found.`);
|
||||
return null;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`❌ [${this.id}] Error getting CSRF token:`, error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async submitPrevCode() {
|
||||
try {
|
||||
const prevCodeData = await this.loadCodeFromLocal();
|
||||
|
||||
if (!prevCodeData) return false;
|
||||
|
||||
await this.page_context.goto(
|
||||
"https://www.langtons.com.au/account/mfalogin?rurl=5"
|
||||
);
|
||||
|
||||
await this.page_context.waitForNavigation({
|
||||
timeout: 8000,
|
||||
waitUntil: "domcontentloaded",
|
||||
});
|
||||
|
||||
if (!(await page.type("#code", code, { delay: 120 }))) {
|
||||
await this.clearCodeFromLocal();
|
||||
|
||||
await this.page_context.goto(configs.WEB_URLS.LANGTONS.LOGIN_URL);
|
||||
return false;
|
||||
}
|
||||
|
||||
const csrfToken = await this.getCsrfToken();
|
||||
|
||||
if (!csrfToken) return false;
|
||||
|
||||
const responsePrevCode = await this.callSubmitPrevCodeApi(
|
||||
prevCodeData.code,
|
||||
csrfToken
|
||||
);
|
||||
|
||||
if (!responsePrevCode || !responsePrevCode.success) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const responseAccount = await this.callSubmitAccountApi(csrfToken);
|
||||
|
||||
if (!responseAccount || !responseAccount.success) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// submit tiếp api login....
|
||||
await this.page_context.goto(this.url);
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error(`❌ [${this.id}] Error submitPrevCode:`, error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async handleLogin() {
|
||||
const page = this.page_context;
|
||||
|
||||
|
|
@ -60,6 +227,13 @@ export class LangtonsApiBid extends ApiBid {
|
|||
return;
|
||||
}
|
||||
|
||||
// // check valid prev code
|
||||
// if (this.isCodeValid()) {
|
||||
// const result = await this.submitPrevCode();
|
||||
|
||||
// if (result) return;
|
||||
// }
|
||||
|
||||
if (fs.existsSync(filePath)) {
|
||||
console.log(`🗑 [${this.id}] Deleting existing file: ${filePath}`);
|
||||
fs.unlinkSync(filePath);
|
||||
|
|
@ -133,6 +307,9 @@ export class LangtonsApiBid extends ApiBid {
|
|||
code,
|
||||
});
|
||||
|
||||
// save code to local
|
||||
// await this.saveCodeToLocal({ name, code });
|
||||
|
||||
// ⌨ Enter verification code
|
||||
console.log(`✍ [${this.id}] Entering verification code...`);
|
||||
await page.type("#code", code, { delay: 120 });
|
||||
|
|
@ -160,6 +337,9 @@ export class LangtonsApiBid extends ApiBid {
|
|||
|
||||
// await page.goto(this.url);
|
||||
console.log(`✅ [${this.id}] Navigation successful!`);
|
||||
|
||||
// clear code
|
||||
// this.clearCodeFromLocal();
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`❌ [${this.id}] Error during login process:`,
|
||||
|
|
|
|||
|
|
@ -1,17 +1,18 @@
|
|||
import * as path from 'path';
|
||||
import { fileURLToPath } from 'url'; // ✅ Cần import từ 'url'
|
||||
import * as path from "path";
|
||||
import { fileURLToPath } from "url"; // ✅ Cần import từ 'url'
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
const CONSTANTS = {
|
||||
PROFILE_PATH: path.join(__dirname, 'profiles'),
|
||||
ERROR_IMAGES_PATH: path.join(__dirname, 'error-images'),
|
||||
TYPE_IMAGE: {
|
||||
ERRORS: 'errors',
|
||||
SUCCESS: 'success',
|
||||
WORK: 'work',
|
||||
},
|
||||
PROFILE_PATH: path.join(__dirname, "profiles"),
|
||||
LOCAL_DATA_PATH: path.join(__dirname, "local-data"),
|
||||
ERROR_IMAGES_PATH: path.join(__dirname, "error-images"),
|
||||
TYPE_IMAGE: {
|
||||
ERRORS: "errors",
|
||||
SUCCESS: "success",
|
||||
WORK: "work",
|
||||
},
|
||||
};
|
||||
|
||||
export default CONSTANTS;
|
||||
|
|
|
|||
|
|
@ -133,6 +133,13 @@ export const getPathProfile = (origin_url) => {
|
|||
);
|
||||
};
|
||||
|
||||
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)) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue