const puppeteer = require("puppeteer"); const axios = require("axios"); const luxon = require("luxon"); const { exit } = require("process"); const fs = require("fs"); const path = require("path"); const dotenv = require("dotenv").config({ path: __dirname + "/.env" }); const utils = require("./utils"); const emailUtils = require("./email-utils"); (async function () { console.log( `===========< STARTED ${utils.getPakistanStandardTime( luxon.DateTime.now() )} >=========` ); const syncDate = luxon.DateTime.now().toFormat("yyyy-MM-dd"); /** * loading config data */ const config = JSON.parse(fs.readFileSync(__dirname + "/config.json")); const environment = process.env["ENVIRONMENT"]; const cryptoConfig = utils.getCryptoConfig(); let rates = []; const email = utils.decryptString( process.env["temu-personal-email"], cryptoConfig.algo, cryptoConfig.key, cryptoConfig.iv ); const password = utils.decryptString( process.env["temu-personal-password"], cryptoConfig.algo, cryptoConfig.key, cryptoConfig.iv ); /* * load cookies */ const loadPageCookies = async function (page) { const cookiesFileName = `cookies.json`; if (fs.existsSync(__dirname + `/cookies/${cookiesFileName}`)) { const cookiesStr = fs.readFileSync( __dirname + `/cookies/${cookiesFileName}` ); const cookies = JSON.parse(cookiesStr); await page.setCookie(...cookies); } }; // launch browser and open page const chromeProfilePath = path.resolve( __dirname, config[environment]["chrome_profile_path"] ); const browser = await puppeteer.launch( utils.getBrowserConfig(chromeProfilePath, environment) ); const page = await browser.newPage(); // await loadPageCookies(page); await page.setViewport({ width: 1600, height: 900, }); // Inject CSS to show the cursor await page.evaluate(() => { const style = document.createElement("style"); style.innerHTML = "* { cursor: auto !important; }"; document.head.appendChild(style); }); // save cookies on page load const cookiesFileName = `cookies.json`; page.on("load", async function () { // save cookies const cookies = await page.cookies(); fs.writeFileSync( __dirname + `/cookies/${cookiesFileName}`, JSON.stringify(cookies, null, 2) ); }); /* * goto login page */ const loginPage = config[environment]["temuLoginPage"]; await page.goto(loginPage, { waitUntil: ["networkidle2"], }); await utils.tryTemuLogin(page, email, password, loginPage); await new Promise((resolve) => setTimeout(resolve, 7000)); // goto orders request page const UnshippedOrdersRequestPage = config[environment]["temuUnshippedOrdersPage"]; await page.goto(UnshippedOrdersRequestPage, { waitUntil: ["networkidle2"], }); const getOrdersFromPage = async (page) => { let orderNumbers = []; try { const orderPOSelector = "div._3GLf87F3"; const orderPoList = await page.$$(orderPOSelector); console.log(`Total Orders On Page ${orderPoList.length}`); if (orderPoList === undefined || orderPoList.length === 0) { console.log("No Unshipped Orders Found !! "); return; } for (const element of orderPoList) { try { const orderNumber = await page.evaluate( (el) => el.textContent.trim(), element ); orderNumbers.push(orderNumber); } catch (e) { console.log(e); } } return orderNumbers; } catch (e) { console.log(`Error in Crawling Orders ${e}`); } return orderNumbers; }; // orders array let orders_list = []; const pagination = 100; let total_items = 0; let currentPage = 1; await new Promise((resolve) => setTimeout(resolve, 2_000)); // set the pagination to 100 / page const pageinationSeletor = "#orders-tab-list > div.y0DVv7GO > div > div._38NAUUfN > div._15QWqbZs > ul > li.PGT_sizeChanger_123 > div > div > div > div > div"; await page.waitForSelector(pageinationSeletor); await page.click(pageinationSeletor); console.log("Clicking on pagination Select"); await new Promise((resolve) => setTimeout(resolve, 3_000)); // select 100 /page option const pagesPerPageSelector = "body > div.PT_outerWrapper_123.PP_outerWrapper_123.ST_dropdown_123.ST_mediumDropdown_123.PT_dropdown_123.PT_portalTopLeft_123.PT_inCustom_123.PP_dropdown_123 > div > div > div > div > ul > li:nth-child(5)"; await page.waitForSelector(pagesPerPageSelector); await page.click(pagesPerPageSelector); await new Promise((resolve) => setTimeout(resolve, 1_000)); if (orders_list.length === 0) { try { // get total items await page .waitForSelector("li.PGT_totalText_123", { timeout: 5000 }) .catch(() => {}); const liText = await page.evaluate(() => { const liElement = document.querySelector("li.PGT_totalText_123"); return liElement ? liElement.textContent : null; }); if (liText === null) { total_items = 10; } else { total_items = parseInt(liText.split(" ")[1]); console.log(`Total Items count : ${total_items}`); } let total_pages = Math.ceil(total_items / pagination); console.log(`Total Pages count : ${total_pages}`); // crawl next pages while (true) { try { console.log(`Crawling for page ${currentPage}`); await utils.tryTemuLogin(page, email, password, loginPage); await new Promise((resolve) => setTimeout(resolve, 4000)); // load cookies await loadPageCookies(page); // get orders let orders = await getOrdersFromPage(page); orders_list.push(...orders); // increment page ++currentPage; // Evaluate the presence of both classes in the
  • element const hasNextBtn = await page.evaluate(() => { const liElement = document.querySelector( "li.PGT_next_123.PGT_disabled_123" ); return liElement == null; }); // break if doesn't have next button if (!hasNextBtn) { console.log("No next button"); break; } if (currentPage > total_pages) { console.log("Last Page Reached"); break; } // goto next page if (hasNextBtn) { await page.evaluate(() => { const liElement = document.querySelector("li.PGT_next_123"); if (liElement) { liElement.click(); } }); } // wait await new Promise((r) => setTimeout(r, 5000)); } catch (e) { console.log(e); break; } } } catch (e) { console.log(e); } } /* * Select shipping date */ const selectShippingDate1 = async (page) => { try { const modalSelector = ".MDL_innerWrapper_123"; // Class selector for the modal await page.waitForSelector(modalSelector); // Optionally, wait for the modal content to be loaded and scrollable const modalContentSelector = ".MDL_body_123"; // Adjust if modal content has a specific class await page.waitForSelector(modalContentSelector); // Scroll to the bottom of the modal content await page.evaluate((modalContentSelector) => { const modalContent = document.querySelector(modalContentSelector); if (modalContent) { modalContent.scrollTo(0, modalContent.scrollHeight); } }, modalContentSelector); const shippingDateSelector = "#packageList\\[0\\]\\.trackingInfoList\\[0\\]\\.shipDate > div > div.Grid_row_123.Grid_rowHorizontal_123.Grid_rowJustifyStart_123.Form_itemContent_123.Form_itemContentCenter_123"; const shippingDateInput = await page.$(shippingDateSelector); if (!shippingDateInput) { console.log("Select not found."); return; } await page.click(shippingDateSelector); await new Promise((resolve) => setTimeout(resolve, 2_000)); const shippingDateOptionSelector = "body > div.PT_outerWrapper_123.PP_outerWrapper_123.ST_dropdown_123.ST_largeDropdown_123.PT_dropdown_123.PT_portalBottomLeft_123.PT_inCustom_123.PP_dropdown_123 > div > div > div > div > div > div:nth-child(1) > ul > li.cIL_item_123.cIL_large_123.cIL_highlight_123.ST_itemRendererLabel_123"; const shippingDateOption = await page.$(shippingDateOptionSelector); if (!shippingDateOption) { console.log("Option not found"); return; } await page.click(shippingDateOptionSelector); console.log("Selected a shipping date"); } catch (error) { console.error("Error selecting shipping date:", error); } }; /* * Select shipping date */ const selectShippingDate = async (page) => { try { console.log("Selecting Dates"); // Selector for the shipping date dropdown const shippingDateSelector = 'div[id="packageList[0].trackingInfoList[0].shipDate"] input:first-of-type'; const shippingDateInput = await page.$(shippingDateSelector); if (shippingDateInput) { console.log("Select found!"); await page.click(shippingDateSelector); const shippingDateOptionSelector = "body > div.PT_outerWrapper_123.PP_outerWrapper_123.ST_dropdown_123.ST_largeDropdown_123.PT_dropdown_123.PT_portalBottomLeft_123.PT_inCustom_123.PP_dropdown_123 > div > div > div > div > div > div:nth-child(1) > ul > li.cIL_item_123.cIL_large_123.cIL_highlight_123.ST_itemRendererLabel_123"; const shippingDateOptionSelectorInput = await page.$( shippingDateOptionSelector ); await page.waitForTimeout(5 * 1000); await new Promise((resolve) => setTimeout(resolve, 5 * 1000)); if (shippingDateOptionSelectorInput) { await shippingDateOptionSelectorInput.click(); console.log("Selected a shipping date"); } else { console.log("Option not found"); } } else { console.log("Select not found."); } } catch (error) { console.error("Error selecting shipping date:", error); } }; /** * Capture response */ const checkShippingRates = async (page, timer) => { return new Promise((resolve, reject) => { // Timeout mechanism to resolve with an empty list after 5 seconds const timeout = setTimeout(() => { page.off("response", handleResponse); // Remove listener on timeout resolve([]); // Resolve with an empty list }, timer); const handleResponse = async (res) => { try { const req = res.request(); if (req.url().includes("/query_shipping_provider_optional")) { const resJson = await res.json(); // Remove listener and clear timeout once response is captured clearTimeout(timeout); page.off("response", handleResponse); resolve(resJson.result.online_channel_vo_list || []); } } catch (ex) { // Remove listener and clear timeout on error clearTimeout(timeout); page.off("response", handleResponse); reject(ex); } }; page.on("response", handleResponse); }); }; /* * check for shipping rate in response */ /* * map response to rates */ const mapResponseToRates = (shippingRates, order) => { for (const shippingRate of shippingRates) { try { let rate = {}; rate["orderId"] = order; rate["channel_id"] = shippingRate["channel_id"]; rate["ship_product_name"] = shippingRate["ship_product_name"]; rate["ship_company_id"] = shippingRate["ship_company_id"]; rate["shipping_company_name"] = shippingRate["shipping_company_name"]; rate["faraway_type"] = shippingRate["faraway_type"]; rate["service_code"] = shippingRate["service_code"]; rate["ship_logistics_type"] = shippingRate["ship_logistics_type"]; rate["require_reservation"] = shippingRate["require_reservation"]; // rate["amount"] = shippingRate["online_estimated_vo"]["charge_amount_si"] / 100_000; rate["currency_type"] = shippingRate["online_estimated_vo"]["currency_type"]; rate["charge_amount_with_currency_str"] = shippingRate["online_estimated_vo"][ "currecharge_amount_with_currency_strncy_type" ]; rate["estimated_delivery_date"] = shippingRate["online_estimated_vo"]["estimated_delivery_date"]; rate["estimated_text"] = shippingRate["online_estimated_vo"]["estimated_text"]; rate["is_selected"] = false; rates.push(rate); } catch (e) { console.log(e); } } }; /* * write rates to file */ const writeToFile = async (data, path) => { fs.writeFileSync(path, JSON.stringify(data, null, 2)); console.log(`Saved JSON to ${path}`); }; /* * check if dimension already already otherwise create new one */ const calculateDimensions = (dailyPickPackMap, skuToQuantityMap) => { let length = 0.0; let width = 0.0; let height = 0.0; for (const [sku, entity] of skuToQuantityMap) { try { const pickPack = dailyPickPackMap.get(sku); length = pickPack.length; width = pickPack.width; height = height + pickPack.height * entity.quantity; } catch (e) { console.log(e); } } return { length: Math.ceil(length), width: Math.ceil(width), height: Math.ceil(height), }; }; // length width height in inches /** * calculate weight */ const calculateSkuWeight = (dailyPickPackMap, skuToQuantityMap) => { try { let weight = 0.0; for (const [sku, entity] of skuToQuantityMap) { const pickPack = dailyPickPackMap.get(sku); weight = weight + pickPack.weight * entity.quantity; } return Math.round(weight); } catch (e) { console.log(e); return 0; } }; const getDimensionString = (length, width, height) => { return `Custom ${length}in x ${width}in x ${height}in`; }; /* * populate fields in form */ const populateDataInFields = async (page, order) => { try { // Selector for all items in the order const itemsSelector = "div.n3xi4S2p"; // Get all item elements const itemElements = await page.$$(itemsSelector); if (!itemElements || itemElements.length === 0) { console.log(`No Items Found in Order ${order}`); return; } // get orders with 1 item with quantity 1 console.log(`Item Quantity : ${itemElements.length}`); if (itemElements.length > 1) { console.log(`${order} : has more then 1 item`); return; } let itemSkuQuantityList = []; // loop over order items and get sku and quantities for (const itemElement of itemElements) { let itemSkuQuantity = {}; const sku = await itemElement .$eval("span._3E6fOFxc:nth-of-type(2)", (skuEl) => skuEl.textContent.trim() ) .catch(() => null); // Catch errors if the element doesn't exist const quantity = await page .$eval("span._3Fs-U187", (element) => { // Extract the text content, split by "Qty:", and trim the result const text = element.parentElement.textContent || ""; return text.replace("Qty:", "").trim(); }) .catch(() => 1); // itemSkuQuantity["sku"] = sku.split("-")[0] ?? ""; itemSkuQuantity["sku"] = sku.substring(0, sku.lastIndexOf("-")) ?? ""; itemSkuQuantity["quantity"] = parseInt(quantity); // check quantity should be 1 as well if (parseInt(quantity) > 1) { console.log("Item has more than quantity"); return; } itemSkuQuantityList.push(itemSkuQuantity); console.log(`Order: ${order}, SKU: ${sku}, Quantity: ${quantity}`); } // date for daily pick pack let date = utils.getFirstDayToCurrentMonth(); if ( itemSkuQuantityList !== undefined && itemSkuQuantityList.length !== 0 ) { // sku str let skuStr = itemSkuQuantityList .filter((item) => item.sku.trim() !== "") .map((item) => item.sku) .join(","); // cosmos url const dailyPickPackUrl = utils.getSkuDailyPickPack(skuStr, date); console.log(dailyPickPackUrl); // request cosmos const axiosConfig = { method: "get", url: dailyPickPackUrl, headers: { "Content-Type": "application/json", }, }; // get pick packs const response = await axios(axiosConfig); const pickPacks = response.data; // sku map const skuMapToPickPack = new Map( pickPacks.map((item) => [item.sku, item]) ); // sku quality map const skuMapToQuatityMap = new Map( itemSkuQuantityList.map((item) => [item.sku, item]) ); // weight selector const weightInputSelector = 'div[id="packageList[0].trackingInfoList[0].weight"] input:first-of-type'; await page.waitForSelector(weightInputSelector); // calculate weight of order items const totalCalWeight = calculateSkuWeight( skuMapToPickPack, skuMapToQuatityMap ); // Type inside the input field await page.type(weightInputSelector, String(totalCalWeight)); await new Promise((resolve) => setTimeout(resolve, 5 * 1000)); // dimension selector const dimensionInputSelector = 'div[id="packageList[0].trackingInfoList[0].sizeInfo"] input:first-of-type'; await page.waitForSelector(dimensionInputSelector); // click on add dimension await page.click(dimensionInputSelector); // wait for 2 seconds await new Promise((resolve) => setTimeout(resolve, 2 * 1000)); let { length, width, height } = calculateDimensions( skuMapToPickPack, skuMapToQuatityMap ); console.log(`length : ${length}`); console.log(`width : ${width}`); console.log(`height : ${height}`); // check for dimension already exists const listItemsSelector = "body > div.PT_outerWrapper_123.PP_outerWrapper_123.ST_dropdown_123.ST_mediumDropdown_123.ST_customItem_123.PT_dropdown_123.PT_portalBottomLeft_123.PT_inCustom_123.PP_dropdown_123 > div > div > div > div > div > div._2A9Ayt3Y > ul > li"; const listItems = await page.$$(listItemsSelector); console.log(`Dimensions count ${listItems.length}`); // Loop through and extract text content or perform actions for (const listItem of listItems) { const text = await page.evaluate( (el) => el.textContent.trim(), listItem ); if (text === getDimensionString(length, width, height)) { console.log(`Clicked on list item with text: ${text}`); await listItem.click(); return; } } // click on Add dimensions await page.click("div._3fps8VlR > div._1IbQdfUN"); // length input const lengthSelector = "#length > div > div.Grid_row_123.Grid_rowHorizontal_123.Grid_rowJustifyStart_123.Form_itemContent_123.Form_itemContentCenter_123 > div > div > div > div.IPT_inputWrapper_123.IPTN_inputWrapper_123.IPT_collapseRight_123 > div > div.IPT_inputBlockCell_123 > input"; await page.waitForSelector(lengthSelector); await page.type(lengthSelector, String(length)); // width input const widthSelector = "#width > div > div.Grid_row_123.Grid_rowHorizontal_123.Grid_rowJustifyStart_123.Form_itemContent_123.Form_itemContentCenter_123 > div > div > div > div.IPT_inputWrapper_123.IPTN_inputWrapper_123.IPT_collapseRight_123 > div > div.IPT_inputBlockCell_123 > input"; await page.waitForSelector(widthSelector); await page.type(widthSelector, String(width)); // height input const heightSelector = "#height > div > div.Grid_row_123.Grid_rowHorizontal_123.Grid_rowJustifyStart_123.Form_itemContent_123.Form_itemContentCenter_123 > div > div > div > div.IPT_inputWrapper_123.IPTN_inputWrapper_123.IPT_collapseRight_123 > div > div.IPT_inputBlockCell_123 > input"; await page.waitForSelector(heightSelector); await page.type(heightSelector, String(height)); // wait for 5 seconds await new Promise((resolve) => setTimeout(resolve, 5 * 1000)); await page.mouse.click(100, 100, { button: "left" }); // click on save btn const saveBtnSelector = "body > div:nth-child(14) > div > div > div > div.MDL_bottom_123 > div.MDL_footer_123 > div > div._3yOxLjm0._2pgGmJ7w._1eT_m6dA > span._2ISpB3A2"; await page.waitForSelector(saveBtnSelector); await page.click(saveBtnSelector); // wait for 3 seconds await new Promise((resolve) => setTimeout(resolve, 3 * 1000)); } } catch (e) { console.log(e); } }; // get orders if (orders_list.length > 0) { try { // goto every order page for (const [index, order] of orders_list.entries()) { console.log( `Syncing Order ${order} ( ${index + 1} / ${orders_list.length} )` ); // try login await utils.tryTemuLogin(page, email, password, loginPage); await new Promise((resolve) => setTimeout(resolve, 2000)); const orderPage = utils.getTemuOrderPage(order); await page.goto(orderPage, { waitUntil: ["networkidle2"], timeout : 60_000 }); await new Promise((resolve) => setTimeout(resolve, 2000)); // check for buy shipping button const buyShippingSelector = "div._3yOxLjm0._2pgGmJ7w.IoqjAtdZ"; const buyShippingBtn = await page.$(buyShippingSelector); if (!buyShippingBtn) { console.log("No Buy Shipping Button found"); continue; } // button exist await buyShippingBtn.click(); console.log("Clicking on Buy Shipping Button"); await new Promise((resolve) => setTimeout(resolve, 5 * 1_000)); await selectShippingDate1(page); const responsePromise = checkShippingRates(page, 10_000); let orderShippingRates = await responsePromise; await new Promise((resolve) => setTimeout(resolve, 2 * 1_000)); if (utils.isEmpty(orderShippingRates)) { console.log(`Shipping Rates not found`); continue; // populate orders details to populate shipping rates const promise = checkShippingRates(page, 10_000); await populateDataInFields(page, order); await new Promise((resolve) => setTimeout(resolve, 2 * 1000)); orderShippingRates = await promise; } // fields are already populate / save the shipping rates mapResponseToRates(orderShippingRates, order); // write the JSON data to a file const outputFilePath = path.join( config[environment].temu_orders_shipping_rates, "/unprocessed", `${order}.json` ); if (rates.length > 1) { // save to rates to file await writeToFile(rates, outputFilePath); } // wait 10 seconds await new Promise((resolve) => setTimeout(resolve, 5 * 1000)); // reinitialize rates rates = []; } } catch (e) { console.log(e); emailUtils.notify(`Sync Temu Orders Shipping Rates`, e.message); } } console.log( `==========< ENDED ${utils.getPakistanStandardTime( luxon.DateTime.now() )} >==========` ); await page.close(); await browser.close(); })();