228 lines
		
	
	
		
			9.3 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
			
		
		
	
	
			228 lines
		
	
	
		
			9.3 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
import fs from 'fs';
 | 
						|
import configs from '../../system/config.js';
 | 
						|
import { 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 1 minute.`));
 | 
						|
            }, 120 * 1000); // 120 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
 | 
						|
            });
 | 
						|
        });
 | 
						|
 | 
						|
    async isLogin() {
 | 
						|
        if (!this.page_context) return false;
 | 
						|
 | 
						|
        const filePath = getPathProfile(this.origin_url);
 | 
						|
 | 
						|
        return !(await this.page_context.$('input[name="loginEmail"]')) && fs.existsSync(filePath);
 | 
						|
    }
 | 
						|
 | 
						|
    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;
 | 
						|
        }
 | 
						|
 | 
						|
        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);
 | 
						|
        }
 | 
						|
 | 
						|
        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 });
 | 
						|
 | 
						|
            // ⌨ 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 });
 | 
						|
 | 
						|
            // ⏳ 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!`);
 | 
						|
        } 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();
 | 
						|
 | 
						|
        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
 | 
						|
    };
 | 
						|
}
 |