tempmail-sdk 1.2.0 → 1.2.2-dev

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/index.ts CHANGED
@@ -1,5 +1,9 @@
1
1
  import * as tempmail from './providers/tempmail';
2
2
  import * as tempmailCn from './providers/tempmail-cn';
3
+ import * as tmpmails from './providers/tmpmails';
4
+ import * as tempmailg from './providers/tempmailg';
5
+ import * as taEasy from './providers/ta-easy';
6
+ import * as tenmailWangtz from './providers/10mail-wangtz';
3
7
  import * as linshiEmail from './providers/linshi-email';
4
8
  import * as linshiyou from './providers/linshiyou';
5
9
  import * as mffac from './providers/mffac';
@@ -19,6 +23,8 @@ import * as minmail from './providers/minmail';
19
23
  import * as vip215 from './providers/vip-215';
20
24
  import * as anonbox from './providers/anonbox';
21
25
  import * as fakeLegal from './providers/fake-legal';
26
+ import * as moakt from './providers/moakt';
27
+ import * as tenminuteOne from './providers/10minute-one';
22
28
  import { Channel, EmailInfo, InternalEmailInfo, Email, GetEmailsResult, GenerateEmailOptions, GetEmailsOptions } from './types';
23
29
  import { withRetry, withRetryWithAttempts } from './retry';
24
30
  import { reportTelemetry } from './telemetry';
@@ -36,7 +42,7 @@ export { Channel, EmailInfo, Email, EmailAttachment, GetEmailsResult, GenerateEm
36
42
  const tokenStore = new WeakMap<EmailInfo, string>();
37
43
  export { normalizeEmail } from './normalize';
38
44
  export { withRetry, withRetryWithAttempts, fetchWithTimeout, RetryOptions } from './retry';
39
- export type { RetryWithAttemptsResult } from './retry';
45
+ export type { RetryWithAttemptsResult, FetchWithTimeoutOptions } from './retry';
40
46
  export { LogLevel, LogHandler, setLogLevel, getLogLevel, setLogger, logger } from './logger';
41
47
  export { SDKConfig, setConfig, getConfig } from './config';
42
48
  export { getSdkVersion } from './version';
@@ -49,7 +55,7 @@ export {
49
55
  } from './providers/linshi-token';
50
56
 
51
57
  /** 所有支持的渠道列表,用于随机选择和遍历 */
52
- const allChannels: Channel[] = ['tempmail', 'tempmail-cn', 'linshi-email', 'linshiyou', 'mffac', 'tempmail-lol', 'chatgpt-org-uk', 'temp-mail-io', 'awamail', 'temporary-email-org', 'mail-tm', 'mail-cx', 'dropmail', 'guerrillamail', 'maildrop', 'smail-pw', 'boomlify', 'minmail', 'vip-215', 'anonbox', 'fake-legal'];
58
+ const allChannels: Channel[] = ['tempmail', 'tempmail-cn', 'tmpmails', 'tempmailg', 'ta-easy', '10mail-wangtz', '10minute-one', 'linshi-email', 'linshiyou', 'mffac', 'tempmail-lol', 'chatgpt-org-uk', 'temp-mail-io', 'awamail', 'temporary-email-org', 'mail-tm', 'mail-cx', 'dropmail', 'guerrillamail', 'maildrop', 'smail-pw', 'boomlify', 'minmail', 'vip-215', 'anonbox', 'fake-legal', 'moakt'];
53
59
 
54
60
  /**
55
61
  * 渠道信息,包含渠道标识、显示名称和对应网站
@@ -67,6 +73,11 @@ export interface ChannelInfo {
67
73
  const channelInfoMap: Record<Channel, ChannelInfo> = {
68
74
  'tempmail': { channel: 'tempmail', name: 'TempMail', website: 'tempmail.ing' },
69
75
  'tempmail-cn': { channel: 'tempmail-cn', name: 'TempMail CN', website: 'tempmail.cn' },
76
+ 'tmpmails': { channel: 'tmpmails', name: 'TmpMails', website: 'tmpmails.com' },
77
+ 'tempmailg': { channel: 'tempmailg', name: 'TempMailG', website: 'tempmailg.com' },
78
+ 'ta-easy': { channel: 'ta-easy', name: 'TA Easy', website: 'ta-easy.com' },
79
+ '10mail-wangtz': { channel: '10mail-wangtz', name: '10mail Wangtz', website: '10mail.wangtz.cn' },
80
+ '10minute-one': { channel: '10minute-one', name: '10 Minute Email', website: '10minutemail.one' },
70
81
  'linshi-email': { channel: 'linshi-email', name: '临时邮箱', website: 'linshi-email.com' },
71
82
  'linshiyou': { channel: 'linshiyou', name: '临时邮', website: 'linshiyou.com' },
72
83
  'tempmail-lol': { channel: 'tempmail-lol', name: 'TempMail LOL', website: 'tempmail.lol' },
@@ -86,6 +97,7 @@ const channelInfoMap: Record<Channel, ChannelInfo> = {
86
97
  'vip-215': { channel: 'vip-215', name: 'VIP 215', website: 'vip.215.im' },
87
98
  'anonbox': { channel: 'anonbox', name: 'Anonbox', website: 'anonbox.net' },
88
99
  'fake-legal': { channel: 'fake-legal', name: 'Fake Legal', website: 'fake.legal' },
100
+ 'moakt': { channel: 'moakt', name: 'Moakt', website: 'moakt.com' },
89
101
  };
90
102
 
91
103
  /**
@@ -200,6 +212,16 @@ async function generateEmailOnce(channel: Channel, options: GenerateEmailOptions
200
212
  return tempmail.generateEmail(options.duration || 30);
201
213
  case 'tempmail-cn':
202
214
  return tempmailCn.generateEmail(options.domain ?? null);
215
+ case 'tmpmails':
216
+ return tmpmails.generateEmail(options.domain ?? null);
217
+ case 'tempmailg':
218
+ return tempmailg.generateEmail(options.domain ?? null);
219
+ case 'ta-easy':
220
+ return taEasy.generateEmail();
221
+ case '10mail-wangtz':
222
+ return tenmailWangtz.generateEmail(options.domain ?? null);
223
+ case '10minute-one':
224
+ return tenminuteOne.generateEmail(options.domain ?? null);
203
225
  case 'linshi-email':
204
226
  return linshiEmail.generateEmail();
205
227
  case 'linshiyou':
@@ -236,6 +258,8 @@ async function generateEmailOnce(channel: Channel, options: GenerateEmailOptions
236
258
  return anonbox.generateEmail();
237
259
  case 'fake-legal':
238
260
  return fakeLegal.generateEmail(options.domain ?? null);
261
+ case 'moakt':
262
+ return moakt.generateEmail(options.domain ?? null);
239
263
  default:
240
264
  throw new Error(`Unknown channel: ${channel}`);
241
265
  }
@@ -314,6 +338,20 @@ async function getEmailsOnce(channel: Channel, email: string, token?: string): P
314
338
  return tempmail.getEmails(email);
315
339
  case 'tempmail-cn':
316
340
  return tempmailCn.getEmails(email);
341
+ case 'tmpmails':
342
+ if (!token) throw new Error('internal error: token missing for tmpmails');
343
+ return tmpmails.getEmails(email, token);
344
+ case 'tempmailg':
345
+ if (!token) throw new Error('internal error: token missing for tempmailg');
346
+ return tempmailg.getEmails(email, token);
347
+ case 'ta-easy':
348
+ if (!token) throw new Error('internal error: token missing for ta-easy');
349
+ return taEasy.getEmails(email, token);
350
+ case '10mail-wangtz':
351
+ return tenmailWangtz.getEmails(email);
352
+ case '10minute-one':
353
+ if (!token) throw new Error('internal error: token missing for 10minute-one');
354
+ return tenminuteOne.getEmails(email, token);
317
355
  case 'linshi-email':
318
356
  if (!token) throw new Error('internal error: token missing for linshi-email');
319
357
  return linshiEmail.getEmails(email, token);
@@ -367,6 +405,9 @@ async function getEmailsOnce(channel: Channel, email: string, token?: string): P
367
405
  return anonbox.getEmails(token, email);
368
406
  case 'fake-legal':
369
407
  return fakeLegal.getEmails(email);
408
+ case 'moakt':
409
+ if (!token) throw new Error('internal error: token missing for moakt');
410
+ return moakt.getEmails(email, token);
370
411
  default:
371
412
  throw new Error(`Unknown channel: ${channel}`);
372
413
  }
package/src/normalize.ts CHANGED
@@ -70,6 +70,7 @@ function normalizeFrom(raw: any): string {
70
70
  raw.from_addr ||
71
71
  raw.from_address ||
72
72
  raw.fromAddress ||
73
+ raw.mail_sender ||
73
74
  raw.sender ||
74
75
  raw.address_from ||
75
76
  raw.from_email ||
@@ -92,7 +93,7 @@ function normalizeTo(raw: any, recipientEmail: string): string {
92
93
  * 候选字段: subject, e_subject
93
94
  */
94
95
  function normalizeSubject(raw: any): string {
95
- return raw.subject || raw.e_subject || '';
96
+ return raw.subject || raw.e_subject || raw.mail_title || '';
96
97
  }
97
98
 
98
99
  /**
@@ -104,6 +105,7 @@ function normalizeText(raw: any): string {
104
105
  raw.text ||
105
106
  raw.text_body ||
106
107
  raw.preview_text ||
108
+ raw.mail_body_text ||
107
109
  raw.body ||
108
110
  raw.content ||
109
111
  raw.body_text ||
@@ -118,7 +120,7 @@ function normalizeText(raw: any): string {
118
120
  * 候选字段: html, html_content, body_html
119
121
  */
120
122
  function normalizeHtml(raw: any): string {
121
- return raw.html || raw.html_body || raw.html_content || raw.body_html || '';
123
+ return raw.html || raw.html_body || raw.html_content || raw.body_html || raw.mail_body_html || '';
122
124
  }
123
125
 
124
126
  /**
@@ -0,0 +1,128 @@
1
+ import { InternalEmailInfo, Email, Channel } from '../types';
2
+ import { normalizeEmail } from '../normalize';
3
+ import { fetchWithTimeout } from '../retry';
4
+
5
+ const CHANNEL: Channel = '10mail-wangtz';
6
+ const ORIGIN = 'https://10mail.wangtz.cn';
7
+ const MAIL_DOMAIN = 'wangtz.cn';
8
+
9
+ const JSON_HEADERS: Record<string, string> = {
10
+ Accept: '*/*',
11
+ 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',
12
+ 'Content-Type': 'application/json; charset=utf-8',
13
+ Origin: ORIGIN,
14
+ Referer: `${ORIGIN}/`,
15
+ token: 'null',
16
+ Authorization: 'null',
17
+ };
18
+
19
+ function localPartFromDomainOption(domain?: string | null): string {
20
+ const s = String(domain ?? '').trim();
21
+ if (!s) return '';
22
+ const at = s.indexOf('@');
23
+ return at >= 0 ? s.slice(0, at).trim() : s;
24
+ }
25
+
26
+ /** emailList 返回 mailparser 风格:{ text?, value?: [{ address, name }] } */
27
+ function formatAddrField(v: unknown): string {
28
+ if (!v || typeof v !== 'object') return '';
29
+ const o = v as { text?: unknown; value?: unknown };
30
+ const text = o.text;
31
+ if (text != null && String(text).trim()) return String(text);
32
+ const value = o.value;
33
+ if (!Array.isArray(value) || value.length === 0) return '';
34
+ const first = value[0];
35
+ if (!first || typeof first !== 'object') return '';
36
+ const addr = String((first as { address?: string }).address ?? '').trim();
37
+ if (!addr) return '';
38
+ const name = String((first as { name?: string }).name ?? '').trim();
39
+ if (name && name.toLowerCase() !== addr.toLowerCase()) return `${name} <${addr}>`;
40
+ return addr;
41
+ }
42
+
43
+ function flattenMessage(raw: Record<string, unknown>): Record<string, unknown> {
44
+ const out: Record<string, unknown> = { ...raw };
45
+ const mid = raw.messageId;
46
+ if (mid != null) out.id = mid;
47
+ const fromStr = formatAddrField(raw.from);
48
+ if (fromStr) out.from = fromStr;
49
+ const toStr = formatAddrField(raw.to);
50
+ if (toStr) out.to = toStr;
51
+ return out;
52
+ }
53
+
54
+ interface TempMailResponse {
55
+ code?: number;
56
+ msg?: string;
57
+ data?: { mailName?: string; endTime?: number };
58
+ }
59
+
60
+ export async function generateEmail(domain?: string | null): Promise<InternalEmailInfo> {
61
+ const emailName = localPartFromDomainOption(domain ?? null);
62
+ const res = await fetchWithTimeout(
63
+ `${ORIGIN}/api/tempMail`,
64
+ {
65
+ method: 'POST',
66
+ headers: {
67
+ ...JSON_HEADERS,
68
+ 'User-Agent':
69
+ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36 Edg/146.0.0.0',
70
+ },
71
+ body: JSON.stringify({ emailName }),
72
+ },
73
+ undefined,
74
+ { skipTlsVerify: true },
75
+ );
76
+ if (!res.ok) {
77
+ throw new Error(`10mail-wangtz generate: ${res.status}`);
78
+ }
79
+ const parsed = (await res.json()) as TempMailResponse;
80
+ if (parsed.code !== 0 || !parsed.data?.mailName) {
81
+ throw new Error(`10mail-wangtz: ${String(parsed.msg ?? 'create failed')}`);
82
+ }
83
+ const local = String(parsed.data.mailName).trim();
84
+ if (!local) {
85
+ throw new Error('10mail-wangtz: empty mailName');
86
+ }
87
+ const email = `${local}@${MAIL_DOMAIN}`;
88
+ let expiresAt: string | undefined;
89
+ if (parsed.data.endTime != null && Number.isFinite(parsed.data.endTime)) {
90
+ expiresAt = new Date(parsed.data.endTime).toISOString();
91
+ }
92
+ return {
93
+ channel: CHANNEL,
94
+ email,
95
+ token: '',
96
+ expiresAt,
97
+ };
98
+ }
99
+
100
+ export async function getEmails(email: string): Promise<Email[]> {
101
+ const res = await fetchWithTimeout(
102
+ `${ORIGIN}/api/emailList`,
103
+ {
104
+ method: 'POST',
105
+ headers: {
106
+ ...JSON_HEADERS,
107
+ 'User-Agent':
108
+ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36 Edg/146.0.0.0',
109
+ },
110
+ body: JSON.stringify({ emailName: email.trim() }),
111
+ },
112
+ undefined,
113
+ { skipTlsVerify: true },
114
+ );
115
+ if (!res.ok) {
116
+ throw new Error(`10mail-wangtz inbox: ${res.status}`);
117
+ }
118
+ const list = (await res.json()) as unknown;
119
+ if (!Array.isArray(list)) {
120
+ throw new Error('10mail-wangtz: inbox response is not an array');
121
+ }
122
+ return list.map(item => {
123
+ if (!item || typeof item !== 'object') {
124
+ return normalizeEmail({}, email);
125
+ }
126
+ return normalizeEmail(flattenMessage(item as Record<string, unknown>), email);
127
+ });
128
+ }
@@ -0,0 +1,258 @@
1
+ import { InternalEmailInfo, Email, Channel } from '../types';
2
+ import { normalizeEmail } from '../normalize';
3
+ import { fetchWithTimeout } from '../retry';
4
+
5
+ const CHANNEL: Channel = '10minute-one';
6
+ const SITE_ORIGIN = 'https://10minutemail.one';
7
+ const API_BASE = 'https://web.10minutemail.one/api/v1';
8
+
9
+ const JWT_RE = /^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/;
10
+
11
+ const DEFAULT_PAGE_HEADERS: Record<string, string> = {
12
+ Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
13
+ 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
14
+ 'Cache-Control': 'no-cache',
15
+ Pragma: 'no-cache',
16
+ DNT: '1',
17
+ 'User-Agent':
18
+ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
19
+ };
20
+
21
+ function extractNuxtDataArray(html: string): unknown[] {
22
+ const m = html.match(/<script[^>]*\bid="__NUXT_DATA__"[^>]*>([\s\S]*?)<\/script>/i);
23
+ if (!m) throw new Error('10minute-one: __NUXT_DATA__ not found');
24
+ return JSON.parse(m[1].trim()) as unknown[];
25
+ }
26
+
27
+ function resolveRef(arr: unknown[], value: unknown, depth = 0): unknown {
28
+ if (depth > 64) throw new Error('10minute-one: resolveRef too deep');
29
+ if (value == null) return value;
30
+ if (typeof value === 'number' && Number.isInteger(value) && value >= 0) {
31
+ if (value >= arr.length) return undefined;
32
+ return resolveRef(arr, arr[value], depth + 1);
33
+ }
34
+ return value;
35
+ }
36
+
37
+ function parseMailServiceTokenFromPayload(arr: unknown[]): string {
38
+ if (!Array.isArray(arr)) throw new Error('10minute-one: invalid __NUXT_DATA__');
39
+
40
+ for (let i = 0; i < Math.min(arr.length, 48); i++) {
41
+ const el = arr[i];
42
+ if (el && typeof el === 'object' && !Array.isArray(el) && 'mailServiceToken' in el) {
43
+ const t = resolveRef(arr, (el as Record<string, unknown>).mailServiceToken);
44
+ if (typeof t === 'string' && JWT_RE.test(t)) return t;
45
+ }
46
+ }
47
+ for (const el of arr) {
48
+ if (el && typeof el === 'object' && !Array.isArray(el) && 'mailServiceToken' in el) {
49
+ const t = resolveRef(arr, (el as Record<string, unknown>).mailServiceToken);
50
+ if (typeof t === 'string' && JWT_RE.test(t)) return t;
51
+ }
52
+ }
53
+ for (const el of arr) {
54
+ if (typeof el === 'string' && JWT_RE.test(el)) return el;
55
+ }
56
+ throw new Error('10minute-one: mailServiceToken not found');
57
+ }
58
+
59
+ function parseQuotedJsonArrayField(html: string, field: string): string[] {
60
+ const key = `${field}:"`;
61
+ const start = html.indexOf(key);
62
+ if (start < 0) return [];
63
+ const sliceStart = start + key.length;
64
+ if (html[sliceStart] !== '[') return [];
65
+ let depth = 0;
66
+ let j = sliceStart;
67
+ for (; j < html.length; j++) {
68
+ const c = html[j];
69
+ if (c === '[') depth++;
70
+ else if (c === ']') {
71
+ depth--;
72
+ if (depth === 0) {
73
+ j++;
74
+ break;
75
+ }
76
+ }
77
+ }
78
+ const raw = html.slice(sliceStart, j);
79
+ const unesc = raw.replace(/\\"/g, '"');
80
+ try {
81
+ const v = JSON.parse(unesc);
82
+ return Array.isArray(v) ? v.map(x => String(x)) : [];
83
+ } catch {
84
+ return [];
85
+ }
86
+ }
87
+
88
+ function parseEmailDomains(html: string): string[] {
89
+ const d = parseQuotedJsonArrayField(html, 'emailDomains');
90
+ return d.filter(Boolean);
91
+ }
92
+
93
+ function parseBlockedUsernames(html: string): Set<string> {
94
+ const list = parseQuotedJsonArrayField(html, 'blockedUsernames');
95
+ return new Set(list.map(s => s.toLowerCase()));
96
+ }
97
+
98
+ function randomRequestId(): string {
99
+ const a = new Uint8Array(16);
100
+ crypto.getRandomValues(a);
101
+ return [...a].map(b => b.toString(16).padStart(2, '0')).join('');
102
+ }
103
+
104
+ function b64DecodeUtf8(b64: string): string {
105
+ if (typeof Buffer !== 'undefined') {
106
+ return Buffer.from(b64, 'base64').toString('utf8');
107
+ }
108
+ const bin = atob(b64);
109
+ const bytes = new Uint8Array(bin.length);
110
+ for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
111
+ return new TextDecoder('utf-8').decode(bytes);
112
+ }
113
+
114
+ function b64UrlPayload(jwt: string): Record<string, unknown> | null {
115
+ const parts = jwt.split('.');
116
+ if (parts.length < 2) return null;
117
+ let b = parts[1].replace(/-/g, '+').replace(/_/g, '/');
118
+ const pad = b.length % 4 ? '='.repeat(4 - (b.length % 4)) : '';
119
+ b += pad;
120
+ try {
121
+ return JSON.parse(b64DecodeUtf8(b)) as Record<string, unknown>;
122
+ } catch {
123
+ return null;
124
+ }
125
+ }
126
+
127
+ function jwtExpMs(jwt: string): number | undefined {
128
+ const p = b64UrlPayload(jwt);
129
+ const exp = p?.exp;
130
+ if (typeof exp === 'number' && Number.isFinite(exp)) return Math.floor(exp * 1000);
131
+ return undefined;
132
+ }
133
+
134
+ function randomLocal(len: number): string {
135
+ const chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
136
+ const a = new Uint8Array(len);
137
+ crypto.getRandomValues(a);
138
+ let s = '';
139
+ for (let i = 0; i < len; i++) s += chars[a[i] % chars.length];
140
+ return s;
141
+ }
142
+
143
+ function pickLocale(domain?: string | null): string {
144
+ const s = String(domain ?? '').trim();
145
+ if (!s) return 'zh';
146
+ if (s.includes('.') && !s.includes('/')) return 'zh';
147
+ return s;
148
+ }
149
+
150
+ function pickMailboxDomain(domainHint: string | undefined, available: string[]): string {
151
+ if (!available.length) throw new Error('10minute-one: no email domains in page');
152
+ const h = (domainHint ?? '').trim().toLowerCase();
153
+ if (h.includes('.')) {
154
+ for (const d of available) {
155
+ if (d.toLowerCase() === h) return d;
156
+ }
157
+ }
158
+ return available[Math.floor(Math.random() * available.length)]!;
159
+ }
160
+
161
+ function apiHeaders(token: string, extra?: Record<string, string>): Record<string, string> {
162
+ const ts = String(Math.floor(Date.now() / 1000));
163
+ return {
164
+ Accept: '*/*',
165
+ 'Accept-Language': DEFAULT_PAGE_HEADERS['Accept-Language']!,
166
+ Authorization: `Bearer ${token}`,
167
+ 'Cache-Control': 'no-cache',
168
+ 'Content-Type': 'application/json',
169
+ DNT: '1',
170
+ Origin: SITE_ORIGIN,
171
+ Pragma: 'no-cache',
172
+ Referer: `${SITE_ORIGIN}/`,
173
+ 'Sec-Fetch-Dest': 'empty',
174
+ 'Sec-Fetch-Mode': 'cors',
175
+ 'Sec-Fetch-Site': 'same-site',
176
+ 'User-Agent': DEFAULT_PAGE_HEADERS['User-Agent']!,
177
+ 'X-Request-ID': randomRequestId(),
178
+ 'X-Timestamp': ts,
179
+ ...extra,
180
+ };
181
+ }
182
+
183
+ function encMailboxPath(email: string): string {
184
+ return encodeURIComponent(email);
185
+ }
186
+
187
+ function itemNeedsDetail(item: Record<string, unknown>): boolean {
188
+ const id = item.id;
189
+ if (id == null || String(id).trim() === '') return false;
190
+ const subj = String(item.subject ?? item.mail_title ?? '').trim();
191
+ const body = String(item.text ?? item.body ?? item.html ?? item.mail_text ?? '').trim();
192
+ return subj === '' && body === '';
193
+ }
194
+
195
+ export async function generateEmail(domain?: string | null): Promise<InternalEmailInfo> {
196
+ const locale = pickLocale(domain ?? undefined);
197
+ const pageUrl = `${SITE_ORIGIN}/${encodeURIComponent(locale)}`;
198
+ const res = await fetchWithTimeout(pageUrl, { headers: { ...DEFAULT_PAGE_HEADERS, Referer: pageUrl } });
199
+ if (!res.ok) throw new Error(`10minute-one generate: ${res.status}`);
200
+ const html = await res.text();
201
+ const arr = extractNuxtDataArray(html);
202
+ const token = parseMailServiceTokenFromPayload(arr);
203
+ let domains = parseEmailDomains(html);
204
+ if (domains.length === 0) {
205
+ domains = ['xghff.com', 'oqqaj.com', 'psovv.com'];
206
+ }
207
+ const blocked = parseBlockedUsernames(html);
208
+ const domainHint = (domain ?? '').trim().includes('.') ? (domain ?? '').trim() : undefined;
209
+ const mailDomain = pickMailboxDomain(domainHint, domains);
210
+
211
+ let local = '';
212
+ for (let attempt = 0; attempt < 32; attempt++) {
213
+ const cand = randomLocal(10);
214
+ if (!blocked.has(cand.toLowerCase())) {
215
+ local = cand;
216
+ break;
217
+ }
218
+ }
219
+ if (!local) throw new Error('10minute-one: could not pick username');
220
+
221
+ const address = `${local}@${mailDomain}`;
222
+ const expMs = jwtExpMs(token);
223
+ return {
224
+ channel: CHANNEL,
225
+ email: address,
226
+ token,
227
+ expiresAt: expMs,
228
+ };
229
+ }
230
+
231
+ export async function getEmails(email: string, token: string): Promise<Email[]> {
232
+ const url = `${API_BASE}/mailbox/${encMailboxPath(email)}`;
233
+ const res = await fetchWithTimeout(url, { headers: apiHeaders(token) });
234
+ if (!res.ok) throw new Error(`10minute-one inbox: ${res.status}`);
235
+ const list = (await res.json()) as unknown;
236
+ if (!Array.isArray(list)) throw new Error('10minute-one: invalid inbox JSON');
237
+
238
+ const out: Email[] = [];
239
+ for (const raw of list) {
240
+ if (!raw || typeof raw !== 'object') continue;
241
+ let row = raw as Record<string, unknown>;
242
+ if (itemNeedsDetail(row)) {
243
+ const id = encodeURIComponent(String(row.id));
244
+ const du = `${API_BASE}/mailbox/${encMailboxPath(email)}/${id}`;
245
+ const dr = await fetchWithTimeout(du, { headers: apiHeaders(token) });
246
+ if (dr.ok) {
247
+ try {
248
+ const detail = (await dr.json()) as Record<string, unknown>;
249
+ row = { ...row, ...detail };
250
+ } catch {
251
+ /* keep row */
252
+ }
253
+ }
254
+ }
255
+ out.push(normalizeEmail(row, email));
256
+ }
257
+ return out;
258
+ }