454 lines
14 KiB
JavaScript
454 lines
14 KiB
JavaScript
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
|
|
};
|
|
}
|