|
|
import { randomBytes } from "node:crypto";
|
|
|
import { qqDnaEmailPool } from "@/lib/flock/qqdna-email-pool";
|
|
|
|
|
|
const PLUS_ALIAS_DOMAINS = new Set([
|
|
|
"gmail.com",
|
|
|
"googlemail.com",
|
|
|
"outlook.com",
|
|
|
"hotmail.com",
|
|
|
"live.com",
|
|
|
"yahoo.com",
|
|
|
"icloud.com",
|
|
|
"me.com",
|
|
|
]);
|
|
|
|
|
|
/** 录制验证可用的 QQ(随机虚构 QQ 会被官网拒绝) */
|
|
|
const VERIFIED_QQ_BASE = "3457189488@qq.com";
|
|
|
|
|
|
/** 轮询取池,避免同次批量撞同一邮箱 */
|
|
|
let poolCursor = 0;
|
|
|
|
|
|
function takeFromPool(pool: string[]): string {
|
|
|
if (pool.length === 0) {
|
|
|
throw new Error("empty email pool");
|
|
|
}
|
|
|
const item = pool[poolCursor % pool.length]!;
|
|
|
poolCursor += 1;
|
|
|
return item;
|
|
|
}
|
|
|
|
|
|
/** 解析逗号/分号/换行分隔的池 */
|
|
|
export function parseFlockEnvPool(raw: string | undefined): string[] {
|
|
|
if (!raw?.trim()) {
|
|
|
return [];
|
|
|
}
|
|
|
return raw
|
|
|
.split(/[,;\n]+/)
|
|
|
.map((s) => s.trim())
|
|
|
.filter(Boolean);
|
|
|
}
|
|
|
|
|
|
function pickPoolItem(pool: string[]): string {
|
|
|
return takeFromPool(pool);
|
|
|
}
|
|
|
|
|
|
function randomUsPhone10(): string {
|
|
|
const areaCodes = [
|
|
|
212, 213, 310, 312, 404, 408, 415, 469, 512, 617, 626, 702, 713, 718, 753,
|
|
|
786, 818, 832, 917, 972,
|
|
|
];
|
|
|
const area = areaCodes[randomBytes(1)[0]! % areaCodes.length]!;
|
|
|
// 避开 N11 保留号段(411 等),否则官网可能禁用提交
|
|
|
let exchange = 200 + (randomBytes(1)[0]! % 800);
|
|
|
if (exchange % 100 === 11) {
|
|
|
exchange += 1;
|
|
|
}
|
|
|
const line = String(1000 + (randomBytes(2).readUInt16BE(0) % 9000));
|
|
|
return `${area}${exchange}${line}`.slice(0, 10);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 短 QQ 邮箱:1/2/3 开头 + 9 位随机数字
|
|
|
* 注意:虚构 QQ 号常被 Flock 以「We had an issue creating your account」拒绝
|
|
|
* 仅 FLOCK_EMAIL_MODE=random_qq 时使用
|
|
|
*/
|
|
|
export function createRandomQqEmail(): string {
|
|
|
const prefix = 1 + (randomBytes(1)[0]! % 3);
|
|
|
const nineDigits = String(randomBytes(4).readUInt32BE(0) % 1_000_000_000).padStart(
|
|
|
9,
|
|
|
"0",
|
|
|
);
|
|
|
return `${prefix}${nineDigits}@qq.com`;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 已验证 QQ + 短别名:3457189488+a1b2c3@qq.com(总计较短)
|
|
|
* 官网曾对该格式成功出过价
|
|
|
*/
|
|
|
export function createVerifiedQqAliasedEmail(): string {
|
|
|
const tag = randomBytes(3).toString("hex"); // 6 字符
|
|
|
return `3457189488+${tag}@qq.com`;
|
|
|
}
|
|
|
|
|
|
/** Gmail/Outlook 等 + 子地址 */
|
|
|
export function buildFlockAliasedEmail(baseEmail: string, unique: string): string {
|
|
|
const trimmed = baseEmail.trim();
|
|
|
const at = trimmed.lastIndexOf("@");
|
|
|
if (at < 1) {
|
|
|
return trimmed;
|
|
|
}
|
|
|
const local = trimmed.slice(0, at).split("+")[0]!;
|
|
|
const domain = trimmed.slice(at + 1).toLowerCase();
|
|
|
const tag =
|
|
|
unique.replace(/[^a-z0-9]/gi, "").slice(0, 8) ||
|
|
|
randomBytes(3).toString("hex");
|
|
|
|
|
|
if (PLUS_ALIAS_DOMAINS.has(domain)) {
|
|
|
return `${local}+${tag}@${domain}`;
|
|
|
}
|
|
|
if (domain === "qq.com") {
|
|
|
return `${local}+${tag.slice(0, 6)}@qq.com`;
|
|
|
}
|
|
|
return createVerifiedQqAliasedEmail();
|
|
|
}
|
|
|
|
|
|
function resolveFreshEmail(id: string, ts: string): string {
|
|
|
const emailPool = parseFlockEnvPool(process.env.FLOCK_EMAIL_POOL);
|
|
|
if (emailPool.length > 0) {
|
|
|
return pickPoolItem(emailPool);
|
|
|
}
|
|
|
|
|
|
const mode = process.env.FLOCK_EMAIL_MODE?.trim().toLowerCase();
|
|
|
// qq.qqdna.com 号码库拼 @qq.com(用户指定探针模式)
|
|
|
if (mode === "qqdna" || mode === "qq_dna") {
|
|
|
return takeFromPool(qqDnaEmailPool());
|
|
|
}
|
|
|
if (mode === "random_qq") {
|
|
|
return createRandomQqEmail();
|
|
|
}
|
|
|
|
|
|
const aliasBase =
|
|
|
process.env.FLOCK_EMAIL_ALIAS_BASE?.trim() ||
|
|
|
process.env.FLOCK_TEST_EMAIL?.trim() ||
|
|
|
VERIFIED_QQ_BASE;
|
|
|
|
|
|
const domain = aliasBase.split("@")[1]?.toLowerCase() ?? "";
|
|
|
if (PLUS_ALIAS_DOMAINS.has(domain) || domain === "qq.com") {
|
|
|
return buildFlockAliasedEmail(aliasBase, `${ts}${id}`);
|
|
|
}
|
|
|
|
|
|
return createVerifiedQqAliasedEmail();
|
|
|
}
|
|
|
|
|
|
const FIRST_NAMES = [
|
|
|
"James",
|
|
|
"Michael",
|
|
|
"Robert",
|
|
|
"David",
|
|
|
"Daniel",
|
|
|
"Emily",
|
|
|
"Sarah",
|
|
|
"Jessica",
|
|
|
"Amanda",
|
|
|
"Lauren",
|
|
|
] as const;
|
|
|
|
|
|
const LAST_NAMES = [
|
|
|
"Johnson",
|
|
|
"Williams",
|
|
|
"Brown",
|
|
|
"Miller",
|
|
|
"Davis",
|
|
|
"Wilson",
|
|
|
"Anderson",
|
|
|
"Taylor",
|
|
|
"Thomas",
|
|
|
"Moore",
|
|
|
] as const;
|
|
|
|
|
|
const COMPANIES = [
|
|
|
"Pacific Freight Solutions",
|
|
|
"Horizon Logistics LLC",
|
|
|
"Summit Cargo Partners",
|
|
|
"Northstar Shipping Co",
|
|
|
"Blue Ridge Transport",
|
|
|
"Atlas Trucking Group",
|
|
|
"Crescent Supply Chain",
|
|
|
"Midwest Load Line",
|
|
|
] as const;
|
|
|
|
|
|
function pickName(list: readonly string[]): string {
|
|
|
return list[randomBytes(1)[0]! % list.length]!;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 每次查价生成新注册账号:
|
|
|
* - 优先 FLOCK_EMAIL_POOL
|
|
|
* - FLOCK_EMAIL_MODE=qqdna:qq.qqdna.com 号码 + @qq.com
|
|
|
* - FLOCK_EMAIL_MODE=random_qq:虚构 {1|2|3}{9位}@qq.com
|
|
|
* - 默认:已验证 QQ + 短 +别名
|
|
|
* 姓名/公司用真人风格(避免 Guest3493 触发风控)
|
|
|
*/
|
|
|
export function createFreshFlockTestAccount(): {
|
|
|
firstName: string;
|
|
|
lastName: string;
|
|
|
company: string;
|
|
|
phone: string;
|
|
|
email: string;
|
|
|
shipmentsPerMonth: string;
|
|
|
} {
|
|
|
const id = randomBytes(5).toString("hex");
|
|
|
const ts = Date.now().toString(36);
|
|
|
|
|
|
const phonePool = parseFlockEnvPool(process.env.FLOCK_PHONE_POOL).map((p) =>
|
|
|
p.replace(/\D/g, "").slice(-10),
|
|
|
);
|
|
|
|
|
|
const email = resolveFreshEmail(id, ts);
|
|
|
const phone =
|
|
|
phonePool.length > 0 ? pickPoolItem(phonePool) : randomUsPhone10();
|
|
|
|
|
|
return {
|
|
|
firstName: process.env.FLOCK_TEST_FIRST_NAME?.trim() || pickName(FIRST_NAMES),
|
|
|
lastName: process.env.FLOCK_TEST_LAST_NAME?.trim() || pickName(LAST_NAMES),
|
|
|
company: process.env.FLOCK_TEST_COMPANY?.trim() || pickName(COMPANIES),
|
|
|
phone,
|
|
|
email,
|
|
|
shipmentsPerMonth:
|
|
|
process.env.FLOCK_TEST_SHIPMENTS_MONTH?.trim() || "1-25",
|
|
|
};
|
|
|
}
|