AI_UIAutomation/utils/common/account-code.helper.ts

98 lines
4.0 KiB
TypeScript

/**
* 临时邮箱取验证码 helper —— 用 mail.tm(免费、免预注册 API key,按需建邮箱拿 JWT)。
* 替代已失效的旧 accountCode.py(mail.td 匿名接口已下线)。
*
* 典型用法(注册/忘记密码/注销):
* const box = await createTempMailbox(); // { address, token }
* // …在 App 里用 box.address 注册、触发发码…
* const code = await getVerificationCode(box.token); // 轮询收件箱,正则取 6 位码
*/
const API = 'https://api.mail.tm';
export interface TempMailbox {
address: string;
password: string;
token: string;
}
async function jget(url: string, token?: string): Promise<any> {
const res = await fetch(url, {
headers: { accept: 'application/json', ...(token ? { Authorization: `Bearer ${token}` } : {}) },
});
if (!res.ok) throw new Error(`GET ${url} → HTTP ${res.status}`);
return res.json();
}
async function jpost(url: string, body: any): Promise<any> {
const res = await fetch(url, {
method: 'POST',
headers: { accept: 'application/json', 'content-type': 'application/json' },
body: JSON.stringify(body),
});
if (!res.ok) throw new Error(`POST ${url} → HTTP ${res.status}`);
return res.json();
}
/** mail.tm 集合响应:accept: application/json 返回普通数组;application/ld+json 返回 {hydra:member}。两者都兼容。 */
function asList(data: any): any[] {
if (Array.isArray(data)) return data;
return data?.['hydra:member'] || [];
}
/** 用已有邮箱地址重新登录 mail.tm 取 token(邮箱持久,可复用已注册账号收码)。 */
export async function getMailboxToken(address: string, password = 'Test123456!'): Promise<string> {
const { token } = await jpost(`${API}/token`, { address, password });
if (!token) throw new Error(`mail.tm token 获取失败: ${address}`);
return token;
}
/** 生成短随机用户名(≤10,字母数字),拼当前可用域名,建临时邮箱并返回登录 token。 */
export async function createTempMailbox(): Promise<TempMailbox> {
const domains = asList(await jget(`${API}/domains`));
const domain = domains.find((d: any) => d.isActive)?.domain || domains[0]?.domain;
if (!domain) throw new Error('mail.tm 无可用域名');
// 随机用户名:字母开头 + 随机串(避免 Date.now,用时间不可用时退随机)
const rand = Math.random().toString(36).slice(2, 10);
const address = `woan${rand}@${domain}`;
const password = 'Test123456!';
await jpost(`${API}/accounts`, { address, password });
const { token } = await jpost(`${API}/token`, { address, password });
if (!token) throw new Error('mail.tm 未返回 token');
console.log(`临时邮箱: ${address}`);
return { address, password, token };
}
/**
* 轮询收件箱取验证码。默认匹配 "code is XXXXXX" 或邮件正文里的 6 位数字。
* @param token createTempMailbox 返回的 JWT
* @param opts.timeoutMs 总超时(默认 60s);opts.regex 自定义验证码正则(第 1 个捕获组为码)
*/
export async function getVerificationCode(
token: string,
opts: { timeoutMs?: number; regex?: RegExp } = {}
): Promise<string> {
const timeoutMs = opts.timeoutMs ?? 60000;
const deadline = Date.now() + timeoutMs;
let lastErr = '';
while (Date.now() < deadline) {
try {
const list = await jget(`${API}/messages`, token);
const msgs = asList(list);
if (msgs.length) {
const detail = await jget(`${API}/messages/${msgs[0].id}`, token);
const text: string = detail.text || (Array.isArray(detail.html) ? detail.html.join(' ') : detail.html || '') || detail.intro || '';
const m = (opts.regex && text.match(opts.regex))
|| text.match(/code is[:\s]*([0-9A-Za-z]{4,8})/i)
|| text.match(/\b(\d{6})\b/);
if (m && m[1]) { console.log(`验证码: ${m[1]}`); return m[1]; }
lastErr = `收到邮件但未匹配到验证码;正文片段: ${text.slice(0, 120)}`;
}
} catch (e: any) {
lastErr = e.message;
}
await new Promise((r) => setTimeout(r, 3000));
}
throw new Error(`取验证码超时(${timeoutMs}ms)。${lastErr}`);
}