import { hasEffectiveFlockLogin, mustForceFlockAccountLogin, } from "@/lib/flock/login-context"; import { createFreshFlockTestAccount } from "@/lib/flock/fresh-account"; /** flock-manual-20260713-152051.js 人工录制验证通过的注册账号 */ export const FLOCK_RECORDING_TEST_ACCOUNT = { firstName: "suiji", lastName: "shengcheng", company: "cs", phone: "7536407420", email: "3457189488@qq.com", shipmentsPerMonth: "1-25", } as const; /** Flock RPA 开关:Mock、显式 true、或本地 development 默认开启 */ export function isFlockRpaEnabled(): boolean { if (process.env.RPA_MOCK_MODE === "true") { return true; } const raw = process.env.FLOCK_RPA_ENABLED?.trim().toLowerCase(); if (raw === "false" || raw === "0" || raw === "no") { return false; } if (raw === "true" || raw === "1" || raw === "yes") { return true; } return process.env.NODE_ENV === "development"; } export function getFlockQuoteUrl(): string { return ( process.env.FLOCK_QUOTE_URL?.trim() || "https://app.flockfreight.com/get-a-quote" ); } export function getFlockStorageStatePath(): string { return ( process.env.FLOCK_STORAGE_STATE_PATH?.trim() || ".rpa/flock-storage.json" ); } /** 点击查询后最多等待报价的毫秒数(默认 20s,超时截图后进入下一组) */ export function getFlockQuoteWaitMs(): number { const n = Number(process.env.FLOCK_QUOTE_WAIT_MS ?? "20000"); return Number.isFinite(n) && n > 0 ? n : 20_000; } /** 等待查价表单出现上限(默认 20s;batch 可设更长) */ export function getFlockFormWaitMs(): number { const n = Number(process.env.FLOCK_FORM_WAIT_MS ?? "20000"); return Number.isFinite(n) && n > 0 ? n : 20_000; } /** 每填一字段停顿(embed 默认 300ms 求快;batch 探针可设 1000) */ export function getFlockFieldPauseMs(): number { const n = Number(process.env.FLOCK_FIELD_PAUSE_MS ?? "300"); return Number.isFinite(n) && n >= 0 ? n : 300; } export { getFlockPollTimeoutMs } from "@/lib/flock/poll-timeout"; /** 成功所需最少报价档数(默认 1:有任意一档即成功) */ export function getFlockMinQuotes(): number { const n = Number(process.env.FLOCK_MIN_QUOTES ?? "1"); return Number.isFinite(n) && n >= 1 ? Math.floor(n) : 1; } /** 单浏览器模式下同一邮箱连续查价次数,满额后换新邮箱+新浏览器(默认不限) */ export function getFlockQuotesPerEmail(): number { const raw = process.env.FLOCK_QUOTES_PER_EMAIL?.trim(); if (!raw) { return Number.POSITIVE_INFINITY; } const n = Number(raw); return Number.isFinite(n) && n >= 1 ? Math.floor(n) : Number.POSITIVE_INFINITY; } /** Worker 单 job 内换邮箱重试(默认 1:快失败;batch 探针可设 3) */ export function getFlockSessionMaxRotations(): number { const n = Number(process.env.FLOCK_SESSION_MAX_ROTATIONS ?? "1"); return Number.isFinite(n) && n >= 1 ? Math.floor(n) : 1; } /** * Flock 报价主路径: * - `direct`:仅 POST /user/quotes * - `dom`:仅 UI RPA * - `direct_then_dom`(默认):Direct 优先,失败再 DOM */ export function getFlockQuoteMode(): | "direct" | "dom" | "direct_then_dom" { const raw = process.env.FLOCK_QUOTE_MODE?.trim().toLowerCase(); if (raw === "direct" || raw === "dom" || raw === "direct_then_dom") { return raw; } return "direct_then_dom"; } export function isFlockDirectQuoteEnabled(): boolean { const mode = getFlockQuoteMode(); return mode === "direct" || mode === "direct_then_dom"; } export function isFlockDomQuoteFallbackEnabled(): boolean { const mode = getFlockQuoteMode(); return mode === "dom" || mode === "direct_then_dom"; } /** * Worker 进程内复用 Flock 浏览器(同邮箱 Build another quote,满额轮换) * 默认:配置了 FLOCK_QUOTES_PER_EMAIL 时开启 */ export function isFlockWorkerReuseSession(): boolean { const raw = process.env.FLOCK_WORKER_REUSE_SESSION?.trim().toLowerCase(); if (raw === "false" || raw === "0" || raw === "no") { return false; } if (raw === "true" || raw === "1" || raw === "yes") { return true; } const perEmail = getFlockQuotesPerEmail(); return Number.isFinite(perEmail) && perEmail < Number.POSITIVE_INFINITY; } export type FlockLoginCredentials = { email: string; password: string; }; /** DOM/RPA 保障路径登录凭据(Direct 匿名 bootstrap 不依赖此项) */ export function getFlockLoginCredentials(): FlockLoginCredentials | null { const email = process.env.FLOCK_LOGIN_EMAIL?.trim() ?? ""; const password = process.env.FLOCK_LOGIN_PASSWORD?.trim() ?? ""; if (!email || !password) { return null; } return { email, password }; } export function hasFlockLoginCredentials(): boolean { return getFlockLoginCredentials() !== null; } export function getFlockLoginUrl(): string { return ( process.env.FLOCK_LOGIN_URL?.trim() || "https://app.flockfreight.com/login" ); } /** * 每次查价使用全新浏览器会话:不加载/不保存 storage,清空 cookie 痕迹 * 配置了 FLOCK_LOGIN_* 时强制关闭(登录态需持久 session) */ export function isFlockFreshSessionPerQuote(): boolean { if (hasEffectiveFlockLogin() || hasFlockLoginCredentials()) { return false; } const raw = process.env.FLOCK_FRESH_SESSION_PER_QUOTE?.trim().toLowerCase(); if (raw === "false" || raw === "0" || raw === "no") { return false; } if (raw === "true" || raw === "1" || raw === "yes") { return true; } return isFlockRandomAccountPerQuote(); } /** * 加载录制会话(默认:存在 flock-storage.json 则加载,跳过注册) * 无痕模式(FLOCK_FRESH_SESSION_PER_QUOTE)下永不加载 */ export function shouldLoadFlockStorageState(fileExists: boolean): boolean { // 客户账密:禁止吃共享 storage(可能是匿名/其他客户会话) if (mustForceFlockAccountLogin()) { return false; } if (isFlockFreshSessionPerQuote()) { return false; } const raw = process.env.FLOCK_LOAD_STORAGE_STATE?.trim().toLowerCase(); if (raw === "false" || raw === "0" || raw === "no") { return false; } return fileExists; } /** * 报价后/登录后是否写 storage * 登录凭据模式默认写回;无痕模式永不写入 */ export function shouldSaveFlockStorageState(): boolean { // 客户账密不写共享文件,避免串客户 if (mustForceFlockAccountLogin()) { return false; } if (isFlockFreshSessionPerQuote()) { return false; } if (hasEffectiveFlockLogin() || hasFlockLoginCredentials()) { const raw = process.env.FLOCK_SAVE_STORAGE_AFTER_QUOTE?.trim().toLowerCase(); if (raw === "false" || raw === "0" || raw === "no") { return false; } return true; } const raw = process.env.FLOCK_SAVE_STORAGE_AFTER_QUOTE?.trim().toLowerCase(); return raw === "true" || raw === "1" || raw === "yes"; } /** * 每次查价换新注册邮箱(需配合无痕模式) * 配置了 FLOCK_LOGIN_* 时关闭(禁止生产 DOM 换号注册) */ export function isFlockRandomAccountPerQuote(): boolean { if (hasEffectiveFlockLogin() || hasFlockLoginCredentials()) { return false; } const raw = process.env.FLOCK_RANDOM_ACCOUNT_PER_QUOTE?.trim().toLowerCase(); return raw === "true" || raw === "1" || raw === "yes"; } export type FlockTestAccount = { firstName: string; lastName: string; company: string; phone: string; email: string; shipmentsPerMonth: string; }; /** @deprecated 使用 createFreshFlockTestAccount */ export function createRandomFlockTestAccount(): FlockTestAccount { return createFreshFlockTestAccount(); } /** * 注册页测试账号 * - 默认:录制验证账号(固定邮箱/电话) * - 无痕/随机模式:每次新邮箱 + 新电话,不依赖 storage */ export function getFlockTestAccount(): FlockTestAccount { if (isFlockFreshSessionPerQuote() || isFlockRandomAccountPerQuote()) { return createFreshFlockTestAccount(); } const rec = FLOCK_RECORDING_TEST_ACCOUNT; return { firstName: process.env.FLOCK_TEST_FIRST_NAME?.trim() || rec.firstName, lastName: process.env.FLOCK_TEST_LAST_NAME?.trim() || rec.lastName, company: process.env.FLOCK_TEST_COMPANY?.trim() || rec.company, phone: (process.env.FLOCK_TEST_PHONE?.replace(/\D/g, "") || rec.phone).slice( -10, ), email: process.env.FLOCK_TEST_EMAIL?.trim() || rec.email, shipmentsPerMonth: process.env.FLOCK_TEST_SHIPMENTS_MONTH?.trim() || rec.shipmentsPerMonth, }; }