import axios from "axios"; import { take } from "es-toolkit"; import { mkdir } from "node:fs/promises"; import { HyundaiCustomerVerifier } from "../hyundai/hyundaiCustomerVerifier"; import { appendRowsToDailySheet } from "../sheet/googlesheetapi"; type MallUseAuthResponse = { STATUS: string; sEncData: string; sEncKey: string; sActionPath: string; }; type Account = { mallId: string; password: string; groupNo: string; division: string; shopNo: string; }; // const INITIAL_COOKIE = // "login_mode=1; PHPSESSID=df4dcdbed2d5be411d1ba57b28fe0572; _fwb=238EQK8z8Dup2CWqAOV9GHr.1777057879401; _gcl_au=1.1.1767964142.1777057879; _fbp=fb.1.1777057879522.988692189750068964; CUK45=cuk45_eckorea24_gkemvii10ntc5fghtaaqf9mg42vscrj4; CUK2Y=cuk2y_eckorea24_gkemvii10ntc5fghtaaqf9mg42vscrj4; CID=CIDR737c085badb1ba6805354e1bed9081de; CIDR737c085badb1ba6805354e1bed9081de=41a12ffaeb4b4614beaf5774fb1aaa15%3A%3A%3A%3A%3A%3A%3A%3A%3A%3A%3A%3A%3A%3A%3A%3A%3A%3A%3A%3A%2F%3A%3A1777057879%3A%3A%3A%3Appdp%3A%3A1777057879%3A%3A%3A%3A%3A%3A%3A%3A; basketcount_1=0; basketprice_1=0%EC%9B%90; wish_id=a6e247fd0eed9199543ec057cb0caf9e; wishcount_1=0; isviewtype=pc; _clck=1eo5hbc%5E2%5Eg5h%5E0%5E2305; analytics_session_id=analytics_session_id.eckorea24_1.E32D67B.1777057879666; analytics_longterm=analytics_longterm.eckorea24_1.B29FF1A.1775390402832; _hjSession_2368957=eyJpZCI6IjBmM2VkMzY4LTk1ZDItNGE3OS1hYjUyLTI2MDNjODhlZTg0YyIsImMiOjE3NzcwNTc4Nzk5MjcsInMiOjAsInIiOjAsInNiIjowLCJzciI6MCwic2UiOjAsImZzIjoxLCJzcCI6MX0=; vt=1777057880; _hjSessionUser_2368957=eyJpZCI6IjQzNmEwOGM5LWY0YmQtNTA3ZS05NTY5LTU2NTk1YjRkZjUwOSIsImNyZWF0ZWQiOjE3NzcwNTc4Nzk5MjYsImV4aXN0aW5nIjp0cnVlfQ==; CVID=CVID.54515f5b4a50510b076f05.1777057880748; CVID_Y=CVID_Y.54515f5b4a50510b076f05.1777057880748; ch-veil-id=5170f9b3-9125-4fa5-bc25-5a0588aa0193; ch-session-193477=eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJzZXMiLCJleHAiOjE3Nzk2NDk4ODEsImlhdCI6MTc3NzA1Nzg4MSwia2V5IjoiMTkzNDc3LTY5ZDQ1MmI1NDc3NjYxODRmZmY4In0.ubvM5Ul3_5n32fs3Zc5oQHAmrVOClz7CFFczwD5Wt-o; _ga_12RF674XCD=GS2.2.s1777057881$o1$g0$t1777057881$j60$l0$h0; _ga_JC3MGH4M4T=GS2.2.s1777057880$o1$g1$t1777057880$j59$l0$h0; _ga=GA1.1.1282638454.1777057025; cto_bundle=Pm-nMl9OcHp0eWh3dmlOSUV0UGV0eHI2cEZ5R0J1c1J0bG1oUDdzdDNISjl3ZHc3ZmVQc215REpDYVFRd0IyZFYlMkZseW9lQ0kxY0NBM2NwJTJGU1RNZ21DR3pITm90TlZCaVlXd0I4JTJCOU9EaGZ6Mkk0TEhURFFiUklsVzJTaDc4OGN4c045eCUyQldOUkluaFdUVGhCaHdJbWl5RCUyQnh3JTNEJTNE; _clsk=49y69s%5E1777057881420%5E1%5E1%5Ee.clarity.ms%2Fcollect; _ga_ZTM1Z99BLE=GS2.2.s1777057880$o1$g1$t1777057882$j57$l0$h0; _ga_Z6CSBGDNRT=GS2.1.s1777057880$o1$g0$t1777057882$j58$l0$h0; GMCC=1dpUipix8zMJMXeWxnayh6U2Z%252Bwn64de5%252BGBE9t8ecAjkaNREGa5nViF811clijfk1asqmTpCYYgWGkGUVnYYiZloR4ksXOD8yT5BB%252FeAxw%253D; _ga_TW9JR58492=GS2.1.s1777057025$o1$g1$t1777057921$j21$l0$h0"; const INITIAL_COOKIE = ""; const RESULT_DIR = "result"; export let cookie = INITIAL_COOKIE; export let dynamicHiddenFieldName = ""; export let dynamicHiddenFieldValue = ""; let activeAccount: Account = { mallId: "", password: "", groupNo: "", division: "", shopNo: "", }; function getTodayKstDateString() { const parts = new Intl.DateTimeFormat("en-CA", { timeZone: "Asia/Seoul", year: "numeric", month: "2-digit", day: "2-digit", }) .formatToParts(new Date()) .reduce>((acc, part) => { if (part.type !== "literal") { acc[part.type] = part.value; } return acc; }, {}); return `${parts.year}-${parts.month}-${parts.day}`; } function isIsoDateString(value: string) { return /^\d{4}-\d{2}-\d{2}$/.test(value); } function parseCliOptions() { const firstArg = Bun.argv[2]?.trim(); const secondArg = Bun.argv[3]?.trim(); if (firstArg && isIsoDateString(firstArg)) { return { regDate: firstArg, accountsPath: secondArg || "./cafe24/accounts.txt", }; } return { regDate: getTodayKstDateString(), accountsPath: firstArg || "./cafe24/accounts.txt", }; } const { regDate, accountsPath } = parseCliOptions(); function getAdminBaseUrl() { return `https://${activeAccount.mallId}.cafe24.com`; } function getShopPath(path: string, shopNo = activeAccount.groupNo) { return `/admin/php/shop${shopNo}${path}`; } function sanitizeFilePart(value: string) { return value.replace(/[^a-zA-Z0-9._-]+/g, "_"); } function getResultPrefix() { return [ sanitizeFilePart(activeAccount.mallId), `shop${sanitizeFilePart(activeAccount.shopNo)}`, `group${sanitizeFilePart(activeAccount.groupNo)}`, sanitizeFilePart(activeAccount.division), ].join("-"); } function getResultPath(fileName: string) { return `${RESULT_DIR}/${fileName}`; } function appendCookie(setCookie: string | string[] | undefined) { const setCookieList = typeof setCookie === "string" ? [setCookie] : setCookie; if (!setCookieList?.length) { return; } const cookieMap = new Map( cookie .split(";") .map((part) => part.trim()) .filter(Boolean) .map((part) => { const equalIndex = part.indexOf("="); return [part.slice(0, equalIndex), part.slice(equalIndex + 1)] as const; }) ); for (const header of setCookieList) { const nameValue = header.split(";")[0]?.trim(); if (!nameValue) { continue; } const equalIndex = nameValue.indexOf("="); if (equalIndex === -1) { continue; } cookieMap.set( nameValue.slice(0, equalIndex), nameValue.slice(equalIndex + 1) ); } cookie = [...cookieMap.entries()] .map(([name, value]) => `${name}=${value}`) .join("; "); } function extractDynamicHiddenField(html: string) { const hiddenInputs = html.match(/]*\btype=["']hidden["'][^>]*>/gi) ?? []; for (const input of hiddenInputs) { const nameMatch = input.match(/\bname=["']([^"']*)["']/i); const valueMatch = input.match(/\bvalue=["']([^"']*)["']/i); const name = nameMatch?.[1] ?? ""; const value = valueMatch?.[1] ?? ""; if ( name && /^eyJ[A-Za-z0-9_-]*\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/.test(value) ) { return { name, value }; } } return { name: "", value: "" }; } function extractUserIdCheckUrl(html: string) { const escapedMallId = activeAccount.mallId.replace( /[.*+?^${}()|[\]\\]/g, "\\$&" ); const urlMatch = html.match( new RegExp( `https?:\\/\\/${escapedMallId}\\.cafe24\\.com\\/admin\\/php\\/user_id_check\\.php[^"'\\\`\\s<>]*`, "i" ) ); return urlMatch?.[0].replaceAll("&", "&") ?? ""; } function mergeRedirectSetCookie(responseDetails: { headers?: Record; }) { appendCookie(responseDetails.headers?.["set-cookie"]); } export async function requestShopLoginPage() { const response = await axios.get("https://eclogin.cafe24.com/Shop/", { headers: { accept: "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "accept-language": "ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7", "cache-control": "no-cache", cookie, pragma: "no-cache", priority: "u=0, i", referer: "https://www.cafe24.com/", "sec-ch-ua": '"Google Chrome";v="147", "Not.A/Brand";v="8", "Chromium";v="147"', "sec-ch-ua-mobile": "?0", "sec-ch-ua-platform": '"Linux"', "sec-fetch-dest": "document", "sec-fetch-mode": "navigate", "sec-fetch-site": "same-site", "sec-fetch-user": "?1", "upgrade-insecure-requests": "1", "user-agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/147.0.0.0 Safari/537.36", }, }); appendCookie(response.headers["set-cookie"]); return response; } export async function requestMallUseAuth() { const form = new URLSearchParams(); form.set("url", "MallUseAuth"); form.set("login_mode", "1"); form.set("mobile", "F"); form.set("onnode", ""); form.set("menu", ""); form.set("submenu", ""); form.set("mode", ""); form.set("c_name", ""); form.set("loan_type", ""); form.set("addsvc_suburl", ""); form.set("appID", ""); form.set("userid", activeAccount.mallId); form.set("EncData", ""); form.set("EncKey", ""); form.set("loginId", activeAccount.mallId); form.set("loginPasswd", activeAccount.password); const response = await axios.post( "https://eclogin.cafe24.com/Shop/", form, { params: { url: "MallUseAuth", }, headers: { accept: "application/json, text/javascript, */*; q=0.01", "accept-language": "ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7", "cache-control": "no-cache", "content-type": "application/x-www-form-urlencoded; charset=UTF-8", cookie, origin: "https://eclogin.cafe24.com", pragma: "no-cache", priority: "u=1, i", referer: "https://eclogin.cafe24.com/Shop/", "sec-ch-ua": '"Google Chrome";v="147", "Not.A/Brand";v="8", "Chromium";v="147"', "sec-ch-ua-mobile": "?0", "sec-ch-ua-platform": '"Linux"', "sec-fetch-dest": "empty", "sec-fetch-mode": "cors", "sec-fetch-site": "same-origin", "user-agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/147.0.0.0 Safari/537.36", "x-requested-with": "XMLHttpRequest", }, } ); appendCookie(response.headers["set-cookie"]); return response; } export async function requestComLogin(auth: MallUseAuthResponse) { const form = new URLSearchParams(); form.set("url", "Run"); form.set("login_mode", "1"); form.set("mobile", "F"); form.set("onnode", ""); form.set("menu", ""); form.set("submenu", ""); form.set("mode", ""); form.set("c_name", ""); form.set("loan_type", ""); form.set("addsvc_suburl", ""); form.set("appID", ""); form.set("userid", activeAccount.mallId); form.set("EncData", auth.sEncData); form.set("EncKey", auth.sEncKey); form.set("loginId", activeAccount.mallId); form.set("loginPasswd", activeAccount.password); const response = await axios.post(auth.sActionPath, form, { maxRedirects: 0, validateStatus: (status) => status >= 200 && status < 400, headers: { accept: "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "accept-language": "ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7", "cache-control": "no-cache", "content-type": "application/x-www-form-urlencoded", cookie, origin: "https://eclogin.cafe24.com", pragma: "no-cache", priority: "u=0, i", referer: "https://eclogin.cafe24.com/", "sec-ch-ua": '"Google Chrome";v="147", "Not.A/Brand";v="8", "Chromium";v="147"', "sec-ch-ua-mobile": "?0", "sec-ch-ua-platform": '"Linux"', "sec-fetch-dest": "document", "sec-fetch-mode": "navigate", "sec-fetch-site": "same-site", "upgrade-insecure-requests": "1", "user-agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/147.0.0.0 Safari/537.36", }, }); appendCookie(response.headers["set-cookie"]); return response; } export async function requestUserIdCheck(html: string) { const userIdCheckUrl = extractUserIdCheckUrl(html); if (!userIdCheckUrl) { throw new Error("user_id_check.php URL을 찾지 못했습니다."); } const response = await axios.get(userIdCheckUrl, { beforeRedirect: (_options, responseDetails) => { mergeRedirectSetCookie(responseDetails); _options.headers.cookie = cookie; }, headers: { accept: "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "accept-language": "ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7", "cache-control": "no-cache", cookie, pragma: "no-cache", priority: "u=0, i", referer: "https://user.cafe24.com/", "sec-ch-ua": '"Google Chrome";v="147", "Not.A/Brand";v="8", "Chromium";v="147"', "sec-ch-ua-mobile": "?0", "sec-ch-ua-platform": '"Linux"', "sec-fetch-dest": "document", "sec-fetch-mode": "navigate", "sec-fetch-site": "same-site", "upgrade-insecure-requests": "1", "user-agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/147.0.0.0 Safari/537.36", }, }); appendCookie(response.headers["set-cookie"]); return response; } export async function requestMemberAdminList() { const response = await axios.get( `${getAdminBaseUrl()}${getShopPath("/c/member_admin_l.php", "1")}`, { validateStatus: (status) => status >= 200 && status < 500, headers: { accept: "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "accept-language": "ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7", "cache-control": "no-cache", cookie, pragma: "no-cache", priority: "u=0, i", referer: `${getAdminBaseUrl()}${getShopPath("/c/center.php", "1")}`, "sec-ch-ua": '"Google Chrome";v="147", "Not.A/Brand";v="8", "Chromium";v="147"', "sec-ch-ua-mobile": "?0", "sec-ch-ua-platform": '"Linux"', "sec-fetch-dest": "document", "sec-fetch-mode": "navigate", "sec-fetch-site": "same-origin", "sec-fetch-user": "?1", "upgrade-insecure-requests": "1", "user-agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/147.0.0.0 Safari/537.36", }, } ); const dynamicHiddenField = extractDynamicHiddenField(response.data); dynamicHiddenFieldName = dynamicHiddenField.name; dynamicHiddenFieldValue = dynamicHiddenField.value; return response; } export async function requestShop2MemberAdminList() { const response = await axios.get( `${getAdminBaseUrl()}${getShopPath("/c/member_admin_l.php")}`, { validateStatus: (status) => status >= 200 && status < 500, headers: { accept: "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "accept-language": "ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7", "cache-control": "no-cache", cookie, pragma: "no-cache", priority: "u=0, i", referer: `${getAdminBaseUrl()}${getShopPath("/c/center.php")}`, "sec-ch-ua": '"Google Chrome";v="147", "Not.A/Brand";v="8", "Chromium";v="147"', "sec-ch-ua-mobile": "?0", "sec-ch-ua-platform": '"Linux"', "sec-fetch-dest": "document", "sec-fetch-mode": "navigate", "sec-fetch-site": "same-origin", "sec-fetch-user": "?1", "upgrade-insecure-requests": "1", "user-agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/147.0.0.0 Safari/537.36", }, } ); appendCookie(response.headers["set-cookie"]); const dynamicHiddenField = extractDynamicHiddenField(response.data); dynamicHiddenFieldName = dynamicHiddenField.name; dynamicHiddenFieldValue = dynamicHiddenField.value; return response; } export async function requestShop2MemberAdminSearch() { const form = new URLSearchParams(); form.set("mode", "search"); form.set("isStandardMode", ""); form.set("m_mode", ""); form.set("is_cti", ""); form.set("ord", "regist_date"); form.set("sort", "ASC"); form.set("page", "1"); form.set("rows", "1000"); form.set("excel_private_auth", "T"); form.set("mg_mode", ""); form.set("is_change_membergrade_sms", "F"); form.set("sSmsMemberGradeManualAuthCustomer", "T"); form.set("mg_group_no_fix_flag", ""); form.set(dynamicHiddenFieldName, dynamicHiddenFieldValue); form.set("search_type", "member_id"); form.set("type", ""); form.set("grp_sel", "0"); form.set("group_no", ""); form.set("is_member_auth", "0"); form.set("input_channel", ""); form.set("entry_path_group", ""); form.set("entry_path", ""); form.set("day_type", "1"); form.set("regist_start_date", regDate); form.set("regist_end_date", regDate); form.set("mem_start_date", "04-25"); form.set("mem_end_date", "04-25"); form.set("age1", ""); form.set("age2", ""); form.set("gender", "1"); form.set("sales_amount", "1"); form.set("sales_type", ""); form.set("min_sales_amount", ""); form.set("max_sales_amount", ""); form.set("ord_date_kind", "order_date"); form.set("ord_start_date", ""); form.set("ord_end_date", ""); form.set("iOrderPrdtNo", ""); form.set("sOrderPrdtName", ""); form.set("login_start_date", ""); form.set("login_end_date", ""); form.set("visit_ip", ""); form.set("s_join_cnt", ""); form.set("e_join_cnt", ""); form.set("s_attend_cnt", ""); form.set("e_attend_cnt", ""); form.set("is_marry", "1"); form.set("child", "1"); form.set("is_sms", "1"); form.set("is_news_mail", "1"); form.set("phone", ""); form.set("mobile", ""); form.set("region", "region_00"); form.set("mileage_type", "avail_mileage"); form.set("mileage1", ""); form.set("mileage2", ""); form.set("start_restore_datetime", ""); form.set("end_restore_datetime", ""); form.set("mid_list[]", ""); form.set("group_list[]", ""); form.set("member_name[]", ""); form.set("mg_group_no", activeAccount.groupNo); form.set("group_no_b", activeAccount.groupNo); await Bun.write( getResultPath(`${getResultPrefix()}-request.txt`), form.toString() ); const response = await axios.post( `${getAdminBaseUrl()}${getShopPath("/c/member_admin_l.php")}`, form, { validateStatus: (status) => status >= 200 && status < 500, headers: { accept: "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "accept-language": "ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7", "cache-control": "no-cache", "content-type": "application/x-www-form-urlencoded", cookie, origin: getAdminBaseUrl(), pragma: "no-cache", priority: "u=0, i", referer: `${getAdminBaseUrl()}${getShopPath("/c/member_admin_l.php")}`, "sec-ch-ua": '"Google Chrome";v="147", "Not.A/Brand";v="8", "Chromium";v="147"', "sec-ch-ua-mobile": "?0", "sec-ch-ua-platform": '"Linux"', "sec-fetch-dest": "document", "sec-fetch-mode": "navigate", "sec-fetch-site": "same-origin", "sec-fetch-user": "?1", "upgrade-insecure-requests": "1", "user-agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/147.0.0.0 Safari/537.36", }, } ); appendCookie(response.headers["set-cookie"]); return response; } function parseAccountLine(line: string, lineNo: number): Account { const values = line.includes("\t") ? line.split("\t") : line.includes("|") ? line.split("|") : line.includes(",") ? line.split(",") : line.trim().split(/\s+/); const [mallId, password, groupNo, division] = values.map((value) => value.trim() ); if (!mallId || !password || !groupNo || !division) { throw new Error( `${lineNo}번째 줄 형식이 잘못됐습니다. 아이디, 비번, 그룹넘버, 구분 순서로 넣어주세요.` ); } return { mallId, password, groupNo, division, shopNo: groupNo, }; } async function readAccounts(path: string) { const text = await Bun.file(path).text(); return text .split(/\r?\n/) .map((line, index) => ({ line: line.trim(), lineNo: index + 1 })) .filter(({ line }) => line && !line.startsWith("#")) .map(({ line, lineNo }) => parseAccountLine(line, lineNo)); } async function runAccount(account: Account) { activeAccount = account; cookie = INITIAL_COOKIE; dynamicHiddenFieldName = ""; dynamicHiddenFieldValue = ""; await requestShopLoginPage(); const authResponse = await requestMallUseAuth(); const loginResponse = await requestComLogin(authResponse.data); await requestUserIdCheck(loginResponse.data); const memberAdminResponse = await requestMemberAdminList(); await requestShop2MemberAdminList(); const shop2MemberAdminResponse = await requestShop2MemberAdminSearch(); const shop2ResponsePath = getResultPath(`${getResultPrefix()}-response.html`); let members: any = extractMembers(shop2MemberAdminResponse.data); console.log("추출행:", members.length); if (members.length === 0) { return; } const cafe24 = new HyundaiCustomerVerifier({}); await cafe24.init(); // members = take(members, 5); members = await cafe24.lookupPhones(members); console.log("작업행:", members.length); const appendResult = await appendRowsToDailySheet({ rows: members, sheetName: account.division, baseDate: regDate, }); console.log( `구글 시트 추가 완료: ${appendResult.targetSheetTitle} / ${appendResult.appendedRows}행` ); await Bun.write(shop2ResponsePath, shop2MemberAdminResponse.data); return { mallId: account.mallId, groupNo: account.groupNo, division: account.division, shopNo: account.shopNo, status: memberAdminResponse.status, shopStatus: shop2MemberAdminResponse.status, responsePath: shop2ResponsePath, requestPath: getResultPath(`${getResultPrefix()}-request.txt`), dynamicHiddenFieldName, }; } await mkdir(RESULT_DIR, { recursive: true }); const accounts = await readAccounts(accountsPath); const results = []; for (const account of accounts) { console.log( `${account.mallId} / shop${account.shopNo} / group ${account.groupNo} 시작` ); results.push(await runAccount(account)); } await Bun.write( getResultPath("results.json"), JSON.stringify(results, null, 2) ); console.log(results); function decodeHtml(value: string) { return value .replace(/&/g, "&") .replace(/</g, "<") .replace(/>/g, ">") .replace(/"/g, '"') .replace(/'/g, "'") .replace(/&#(\d+);/g, (_, code) => String.fromCharCode(Number(code))) .replace(/&#x([0-9a-f]+);/gi, (_, code) => String.fromCharCode(parseInt(code, 16)) ); } function normalizeText(value: string) { return decodeHtml(value.replace(/<[^>]*>/g, " ")) .replace(/\s+/g, " ") .trim(); } function getInputValue(rowHtml: string, name: string) { const escapedName = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); const pattern = new RegExp( `]*\\bname=["']${escapedName}["'])[^>]*\\bvalue=["']([^"']*)["'][^>]*>`, "i" ); return decodeHtml(rowHtml.match(pattern)?.[1] ?? "").trim(); } function getCellText(rowHtml: string, index: number) { const cells = [...rowHtml.matchAll(/]*>([\s\S]*?)<\/td>/gi)]; return normalizeText(cells[index]?.[1] ?? ""); } export function extractMembers(html: string): MemberRow[] { const tbodyHtml = html.match(/]*>([\s\S]*?)<\/tbody>/i)?.[1] ?? ""; const rows = tbodyHtml.match(/ rowHtml.includes('name="mid_list[]"')) .map((rowHtml) => { const landline = getCellText(rowHtml, 5); const mobile = getCellText(rowHtml, 6); return { id: getInputValue(rowHtml, "mid_list[]"), full_name: getInputValue(rowHtml, "member_name[]"), created_time: getCellText(rowHtml, 1), phone_number: mobile || landline, }; }); } type MemberRow = { id: string; full_name: string; created_time: string; phone_number: string; };