bid-tool/auto-bid-tool/index.js

355 lines
9.8 KiB
JavaScript

import "dotenv/config";
import _ from "lodash";
import pLimit from "p-limit";
import { io } from "socket.io-client";
import {
createApiBid,
createBidProduct,
deleteProfile,
shouldUpdateProductTab,
} from "./service/app-service.js";
import browser from "./system/browser.js";
import configs from "./system/config.js";
import { delay, isTimeReached, safeClosePage } from "./system/utils.js";
import { updateLoginStatus } from "./system/apis/bid.js";
global.IS_CLEANING = true;
let MANAGER_BIDS = [];
const activeTasks = new Set();
const handleUpdateProductTabs = (data) => {
if (!Array.isArray(data)) {
console.log("Data must be array");
return;
}
const managerBidMap = new Map(MANAGER_BIDS.map((bid) => [bid.id, bid]));
const newDataManager = data.map(({ children, ...web }) => {
const prevApiBid = managerBidMap.get(web.id);
const newChildren = children.map((item) => {
const prevProductTab = prevApiBid?.children.find((i) => i.id === item.id);
if (prevProductTab) {
prevProductTab.setNewData(item);
return prevProductTab;
}
return createBidProduct(web, item);
});
if (prevApiBid) {
prevApiBid.setNewData({ children: newChildren, ...web });
return prevApiBid;
}
return createApiBid({ ...web, children: newChildren });
});
MANAGER_BIDS = newDataManager;
};
const tracking = async () => {
console.log("🚀 Tracking process started...");
while (true) {
try {
console.log("🔍 Scanning active bids...");
const productTabs = _.flatMap(MANAGER_BIDS, "children");
await Promise.allSettled(
MANAGER_BIDS.filter((bid) => !bid.page_context).map((apiBid) => {
console.log(`🎧 Listening to events for API Bid ID: ${apiBid.id}`);
return apiBid.listen_events();
})
);
Promise.allSettled(
productTabs.map(async (productTab) => {
console.log(`📌 Processing Product ID: ${productTab.id}`);
// Xác định parent context
if (!productTab.parent_browser_context) {
const parent = _.find(MANAGER_BIDS, { id: productTab.web_bid.id });
productTab.parent_browser_context = parent?.browser_context;
if (!productTab.parent_browser_context) {
console.log(
`⏳ Waiting for parent process... (Product ID: ${productTab.id})`
);
return;
}
}
// Kết nối Puppeteer nếu chưa có page_context
if (!productTab.page_context) {
console.log(
`🔌 Connecting to page for Product ID: ${productTab.id}`
);
await productTab.puppeteer_connect();
}
// Kiểm tra URL và điều hướng nếu cần
if ((await productTab.page_context.url()) !== productTab.url) {
console.log(
`🔄 Redirecting to new URL for Product ID: ${productTab.id}`
);
await productTab.gotoLink();
}
// Cập nhật nếu cần thiết
if (shouldUpdateProductTab(productTab)) {
console.log(`🔄 Updating Product ID: ${productTab.id}...`);
await productTab.update();
} else {
console.log(
`⏳ Product ID: ${productTab.id} was updated recently. Skipping update.`
);
}
// Chờ first bid
if (!productTab.first_bid) {
console.log(
`🎯 Waiting for first bid for Product ID: ${productTab.id}`
);
return;
}
// Kiểm tra thời gian bid
if (
productTab.start_bid_time &&
!isTimeReached(productTab.start_bid_time)
) {
console.log(
`⏳ Not yet time to bid. Skipping Product ID: ${productTab.id}`
);
return;
}
// Thực thi hành động
console.log(`🚀 Executing action for Product ID: ${productTab.id}`);
await productTab.action();
})
);
// Dọn dẹp tab không dùng
console.log("🧹 Cleaning up unused tabs...");
clearLazyTab();
// Cập nhật trạng thái tracking
console.log("📊 Tracking work status...");
workTracking();
// Bắn event status login
console.log("📊 Tracking login status...");
trackingLoginStatus();
} catch (error) {
console.error("❌ Error in tracking loop:", error);
}
console.log(
`⏳ Waiting ${
configs.AUTO_TRACKING_DELAY / 1000
} seconds before the next iteration...`
);
await delay(configs.AUTO_TRACKING_DELAY);
}
};
const clearLazyTab = async () => {
if (!global.IS_CLEANING) {
console.log("🚀 Cleaning flag is OFF. Proceeding with operation.");
return;
}
if (!browser) {
console.warn("⚠️ Browser is not available or disconnected.");
return;
}
try {
const pages = await browser.pages();
// Lấy danh sách URL từ flattenedArray
const activeUrls = _.flatMap(MANAGER_BIDS, (item) => [
item.url,
...item.children.map((child) => child.url),
]).filter(Boolean); // Lọc bỏ null hoặc undefined
console.log(
"🔍 Page URLs:",
pages.map((page) => page.url())
);
for (const page of pages) {
const pageUrl = page.url();
// 🔥 Bỏ qua tab 'about:blank' hoặc tab không có URL
if (!pageUrl || pageUrl === "about:blank") continue;
if (!activeUrls.includes(pageUrl)) {
if (!page.isClosed() && browser.isConnected()) {
try {
const bidData = MANAGER_BIDS.filter((item) => item.page_context)
.map((i) => ({
current_url: i.page_context.url(),
data: i,
}))
.find((j) => j.current_url === pageUrl);
console.log(bidData);
if (bidData && bidData.data) {
await safeClosePage(bidData.data);
} else {
await page.close();
}
console.log(`🛑 Closing unused tab: ${pageUrl}`);
} catch (err) {
console.warn(`⚠️ Error closing tab ${pageUrl}:, err.message`);
}
}
}
}
} catch (err) {
console.error("❌ Error in clearLazyTab:", err.message);
}
};
const workTracking = async () => {
try {
const activeData = _.flatMap(MANAGER_BIDS, (item) => [
item,
...item.children,
]);
const limit = pLimit(5);
await Promise.allSettled(
activeData
.filter((item) => item.page_context && !item.page_context.isClosed())
.filter((item) => !activeTasks.has(item.id))
.map((item) =>
limit(async () => {
activeTasks.add(item.id);
try {
await item.handleTakeWorkSnapshot();
} catch (error) {
console.error(
`[❌ ERROR] Snapshot failed for Product ID: ${item.id}`,
error
);
} finally {
activeTasks.delete(item.id);
}
})
)
);
} catch (error) {
console.error(
`[❌ ERROR] Work tracking failed: ${error.message}\n`,
error.stack
);
}
};
const trackingLoginStatus = async () => {
try {
if (!MANAGER_BIDS?.length) return;
const results = await Promise.allSettled(
MANAGER_BIDS.map(async (item) => {
try {
const login_status = await item.isLogin();
await updateLoginStatus({
data: {
id: item.id,
type: item.type,
origin_url: item.origin_url,
},
login_status,
});
} catch (err) {
console.warn(
`[⚠️ WARN] Failed to check login for bid ${
item?.id || "unknown"
}: ${err.message}`
);
}
})
);
// Optional: log summary
const failed = results.filter((r) => r.status === "rejected").length;
if (failed) {
console.warn(`[⚠️ WARN] ${failed} login status checks failed.`);
}
} catch (error) {
console.error(
`[❌ ERROR] Login status tracking failed: ${error.message}\n`,
error.stack
);
}
};
(async () => {
const socket = io(`${configs.SOCKET_URL}/bid-ws`, {
transports: ["websocket"],
reconnection: true,
extraHeaders: {
Authorization: process.env.CLIENT_KEY,
},
});
// set socket on global app
global.socket = socket;
// listen connect
socket.on("connect", () => {
console.log("✅ Connected to WebSocket server");
console.log("🔗 Socket ID:", socket.id);
});
// listen connect
socket.on("disconnect", () => {
console.log("❌Client key is valid. Disconnected");
});
// listen event
socket.on("bidsUpdated", async (data) => {
console.log("📢 Bids Data:", data);
handleUpdateProductTabs(data);
});
socket.on("webUpdated", async (data) => {
console.log("📢 Account was updated:", data);
const isDeleted = deleteProfile(data);
if (isDeleted) {
console.log("✅ Profile deleted successfully!");
const tab = MANAGER_BIDS.find((item) => item.url === data.url);
if (!tab) return;
global.IS_CLEANING = false;
await Promise.all(tab.children.map((tab) => safeClosePage(tab)));
await safeClosePage(tab);
global.IS_CLEANING = true;
} else {
console.log("⚠️ No profile found to delete.");
}
});
// AUTO TRACKING
tracking();
})();