import fs from "fs"; import configs from "../../system/config.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"; export class LangtonsApiBid extends ApiBid { reloadInterval = null; constructor({ ...prev }) { super(prev); } waitVerifyData = async () => new Promise((rev, rej) => { // Tạo timeout để reject sau 1 phút nếu không có phản hồi const timeout = setTimeout(() => { global.socket.off(`verify-code.${this.origin_url}`); // Xóa listener tránh rò rỉ bộ nhớ rej( new Error( `[${this.id}] Timeout: No verification code received within 3 minute.` ) ); }, 180 * 1000); // 180 giây global.socket.on(`verify-code.${this.origin_url}`, async (data) => { console.log(`📢 [${this.id}] VERIFY CODE:`, data); clearTimeout(timeout); // Hủy timeout vì đã nhận được mã global.socket.off(`verify-code.${this.origin_url}`); // Xóa listener tránh lặp lại rev(data); // Resolve với dữ liệu nhận được }); }); isLogin = async () => { if (!this.page_context) return false; const filePath = getPathProfile(this.origin_url); return ( !(await this.page_context.$('input[name="loginEmail"]')) && fs.existsSync(filePath) ); }; 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() }; } }, { username: 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 submitCode({ name, code }) { try { const csrfToken = await this.getCsrfToken(); if (!csrfToken) return false; const responsePrevCode = await this.callSubmitPrevCodeApi( code, csrfToken ); if (!responsePrevCode || !responsePrevCode.success) { return false; } const responseAccount = await this.callSubmitAccountApi(csrfToken); if (!responseAccount || !responseAccount.success) { return false; } return true; } catch (error) { console.error(`❌ [${this.id}] Error submitPrevCode:`, error); return false; } } async handleLogin() { const page = this.page_context; global.IS_CLEANING = false; const filePath = getPathProfile(this.origin_url); await page.waitForNavigation({ waitUntil: "domcontentloaded" }); // 🛠 Check if already logged in (login input should not be visible or profile exists) if ( !(await page.$('input[name="loginEmail"]')) && fs.existsSync(filePath) ) { console.log(`✅ [${this.id}] Already logged in, skipping login process.`); 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); } const children = this.children.filter((item) => item.page_context); console.log( `🔍 [${this.id}] Found ${children.length} child pages to close.` ); if (children.length > 0) { console.log(`🛑 [${this.id}] Closing child pages...`); await Promise.all( children.map((item) => { console.log( `➡ [${this.id}] Closing child page with context: ${item.page_context}` ); return safeClosePage(item); }) ); console.log( `➡ [${this.id}] Closing main page context: ${this.page_context}` ); await safeClosePage(this); await this.onCloseLogin(this); } console.log(`🔑 [${this.id}] Starting login process...`); try { // ⌨ Enter email console.log(`✍ [${this.id}] Entering email:`, this.username); await page.type('input[name="loginEmail"]', this.username, { delay: 100, }); // ⌨ Enter password console.log(`✍ [${this.id}] Entering password...`); await page.type('input[name="loginPassword"]', this.password, { delay: 150, }); // ✅ Click the "Remember Me" checkbox console.log(`🔘 [${this.id}] Clicking the "Remember Me" checkbox`); await page.click("#rememberMe", { delay: 80 }); // 🚀 Click the login button console.log(`🔘 [${this.id}] Clicking the "Login" button`); await page.click("#loginFormSubmitButton", { delay: 92 }); // ⏳ Wait for navigation after login console.log(`⏳ [${this.id}] Waiting for navigation after login...`); await page.waitForNavigation({ timeout: 8000, waitUntil: "domcontentloaded", }); console.log(`🌍 [${this.id}] Current page after login:`, page.url()); // 📢 Listen for verification code event console.log( `👂 [${this.id}] Listening for event: verify-code.${this.origin_url}` ); // ⏳ Wait for verification code from socket event const { name, code } = await this.waitVerifyData(); console.log(`✅ [${this.id}] Verification code received:`, { name, 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 }); // // 🚀 Click the verification confirmation button // console.log( // `🔘 [${this.id}] Clicking the verification confirmation button` // ); // await page.click(".btn.btn-block.btn-primary", { delay: 90 }); const reuslt = await this.submitCode({ name, code }); if (!reuslt) { console.log(`[${this.id}] Wrote verifi code failure`); return; } // // ⏳ Wait for navigation after verification // console.log( // `⏳ [${this.id}] Waiting for navigation after verification...` // ); // await page.waitForNavigation({ // timeout: 15000, // waitUntil: "domcontentloaded", // }); await page.goto(this.url, { waitUntil: "networkidle2" }); // 📂 Save session context to avoid re-login await this.saveContext(); console.log(`✅ [${this.id}] Login successful!`); // 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:`, error.message ); } finally { global.IS_CLEANING = true; } } async getWonList() { try { await page.waitForSelector(".row.account-product-list", { timeout: 30000, }); const items = await page.evaluate(() => { return Array.from( document.querySelectorAll(".row.account-product-list") ).map((item) => item.getAttribute("data-lotid") || null); }); return _.compact(items); } catch (error) { return []; } } async handleUpdateWonItem() { console.log(`🔄 [${this.id}] Starting to update the won list...`); // Lấy danh sách các lot_id thắng const items = await this.getWonList(); console.log(`📌 [${this.id}] List of won lot_ids:`, items); // Nếu không có item nào, thoát ra if (items.length === 0) { console.log(`⚠️ [${this.id}] No items to update.`); return; } // Lọc danh sách `this.children` chỉ giữ lại những item có trong danh sách thắng const result = _.filter(this.children, (item) => _.includes(items, item.lot_id) ); console.log( `✅ [${this.id}] ${result.length} items need to be updated:`, result ); // Gọi API updateStatusByPrice cho mỗi item và đợi tất cả hoàn thành const responses = await Promise.allSettled( result.map((i) => updateStatusByPrice(i.id, i.current_price)) ); // Log kết quả của mỗi request responses.forEach((response, index) => { if (response.status === "fulfilled") { console.log(`✔️ [${this.id}] Successfully updated:`, result[index]); } else { console.error( `❌ [${this.id}] Update failed:`, result[index], response.reason ); } }); console.log(`🏁 [${this.id}] Finished updating the won list.`); return responses; } action = async () => { try { const page = this.page_context; page.on("response", async (response) => { const request = response.request(); if (request.redirectChain().length > 0) { if (response.url().includes(configs.WEB_CONFIGS.LANGTONS.LOGIN_URL)) { await this.handleLogin(); } } }); await page.goto(this.url, { waitUntil: "networkidle2" }); await page.bringToFront(); // Set userAgent await page.setUserAgent( "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" ); } catch (error) { console.log("Error [action]: ", error.message); } }; listen_events = async () => { if (this.page_context) return; // await this.puppeteer_connect(); // await this.action(); const results = await this.handlePrevListen(); if (!results) return; this.reloadInterval = setInterval(async () => { try { if (this.page_context && !this.page_context.isClosed()) { console.log(`🔄 [${this.id}] Reloading page...`); await this.page_context.reload({ waitUntil: "networkidle2" }); console.log(`✅ [${this.id}] Page reloaded successfully.`); // this.handleUpdateWonItem(); } else { console.log( `❌ [${this.id}] Page context is closed. Stopping reload.` ); clearInterval(this.reloadInterval); } } catch (error) { console.error(`🚨 [${this.id}] Error reloading page:`, error.message); } }, 60000); // 1p reload }; }