googlesheet

This commit is contained in:
kjy
2026-05-12 22:37:36 +09:00
commit a77e19e4dd
22 changed files with 81174 additions and 0 deletions

710
cafe24/cafe24.ts Normal file
View File

@@ -0,0 +1,710 @@
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<Record<string, string>>((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(/<input\b[^>]*\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("&amp;", "&") ?? "";
}
function mergeRedirectSetCookie(responseDetails: {
headers?: Record<string, string | string[]>;
}) {
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<MallUseAuthResponse>(
"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<string>(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<string>(
`${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<string>(
`${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<string>(
`${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(/&amp;/g, "&")
.replace(/&lt;/g, "<")
.replace(/&gt;/g, ">")
.replace(/&quot;/g, '"')
.replace(/&#039;/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(
`<input\\b(?=[^>]*\\bname=["']${escapedName}["'])[^>]*\\bvalue=["']([^"']*)["'][^>]*>`,
"i"
);
return decodeHtml(rowHtml.match(pattern)?.[1] ?? "").trim();
}
function getCellText(rowHtml: string, index: number) {
const cells = [...rowHtml.matchAll(/<td\b[^>]*>([\s\S]*?)<\/td>/gi)];
return normalizeText(cells[index]?.[1] ?? "");
}
export function extractMembers(html: string): MemberRow[] {
const tbodyHtml =
html.match(/<tbody class=["']center["'][^>]*>([\s\S]*?)<\/tbody>/i)?.[1] ??
"";
const rows = tbodyHtml.match(/<tr\b[\s\S]*?(?=<tr\b|$)/gi) ?? [];
return rows
.filter((rowHtml) => 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;
};