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/README.md +9 -3
- package/dist/index.d.ts +1 -1
- package/dist/index.js +48 -2
- package/dist/normalize.js +5 -3
- package/dist/providers/10mail-wangtz.d.ts +3 -0
- package/dist/providers/10mail-wangtz.js +117 -0
- package/dist/providers/10minute-one.d.ts +3 -0
- package/dist/providers/10minute-one.js +264 -0
- package/dist/providers/moakt.d.ts +3 -0
- package/dist/providers/moakt.js +214 -0
- package/dist/providers/ta-easy.d.ts +5 -0
- package/dist/providers/ta-easy.js +55 -0
- package/dist/providers/tempmailg.d.ts +7 -0
- package/dist/providers/tempmailg.js +202 -0
- package/dist/providers/tmpmails.d.ts +3 -0
- package/dist/providers/tmpmails.js +169 -0
- package/dist/retry.d.ts +5 -1
- package/dist/retry.js +25 -3
- package/dist/types.d.ts +1 -1
- package/dist/types.js +1 -1
- package/package.json +2 -2
- package/src/index.ts +43 -2
- package/src/normalize.ts +4 -2
- package/src/providers/10mail-wangtz.ts +128 -0
- package/src/providers/10minute-one.ts +258 -0
- package/src/providers/moakt.ts +215 -0
- package/src/providers/ta-easy.ts +56 -0
- package/src/providers/tempmailg.ts +208 -0
- package/src/providers/tmpmails.ts +168 -0
- package/src/retry.ts +36 -1
- package/src/types.ts +1 -1
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
|
+
}
|