tempmail-sdk 1.1.2 → 1.1.4
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 +43 -38
- package/demo/poll-emails.ts +290 -28
- package/dist/config.d.ts +16 -0
- package/dist/config.js +4 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.js +33 -16
- package/dist/providers/awamail.js +4 -3
- package/dist/providers/chatgpt-org-uk.d.ts +1 -1
- package/dist/providers/chatgpt-org-uk.js +188 -20
- package/dist/providers/dropmail.js +135 -3
- package/dist/providers/guerrillamail.js +4 -3
- package/dist/providers/linshi-email.d.ts +1 -1
- package/dist/providers/linshi-email.js +19 -7
- package/dist/providers/linshi-token.d.ts +25 -0
- package/dist/providers/linshi-token.js +69 -0
- package/dist/providers/mail-tm.js +43 -25
- package/dist/providers/maildrop.js +3 -2
- package/dist/providers/smail-pw.d.ts +9 -0
- package/dist/providers/smail-pw.js +356 -0
- package/dist/providers/temp-mail-io.js +5 -4
- package/dist/providers/tempmail-lol.js +4 -3
- package/dist/providers/tempmail.js +4 -3
- package/dist/retry.d.ts +2 -10
- package/dist/retry.js +41 -10
- package/dist/types.d.ts +6 -1
- package/dist/types.js +1 -1
- package/package.json +1 -1
- package/src/config.ts +16 -0
- package/src/index.ts +31 -14
- package/src/providers/awamail.ts +3 -2
- package/src/providers/chatgpt-org-uk.ts +213 -22
- package/src/providers/dropmail.ts +162 -2
- package/src/providers/guerrillamail.ts +3 -2
- package/src/providers/linshi-email.ts +24 -7
- package/src/providers/linshi-token.ts +86 -0
- package/src/providers/mail-tm.ts +43 -24
- package/src/providers/maildrop.ts +2 -1
- package/src/providers/smail-pw.ts +382 -0
- package/src/providers/temp-mail-io.ts +4 -3
- package/src/providers/tempmail-lol.ts +3 -2
- package/src/providers/tempmail.ts +3 -2
- package/src/retry.ts +42 -9
- package/src/types.ts +6 -1
- package/test/example.ts +183 -4
- package/dist/providers/tempmail-la.d.ts +0 -15
- package/dist/providers/tempmail-la.js +0 -89
- package/src/providers/tempmail-la.ts +0 -99
package/src/providers/mail-tm.ts
CHANGED
|
@@ -1,14 +1,43 @@
|
|
|
1
1
|
import { InternalEmailInfo, Email, Channel } from '../types';
|
|
2
2
|
import { normalizeEmail } from '../normalize';
|
|
3
|
+
import { fetchWithTimeout } from '../retry';
|
|
3
4
|
|
|
4
5
|
const CHANNEL: Channel = 'mail-tm';
|
|
5
6
|
const BASE_URL = 'https://api.mail.tm';
|
|
6
7
|
|
|
8
|
+
/**
|
|
9
|
+
* 与 Internxt 临时邮箱页(https://internxt.com/temporary-email)前端一致:
|
|
10
|
+
* 同源 cross-site 请求 api.mail.tm 时常带 Origin/Referer;domains 可为空 Bearer。
|
|
11
|
+
*/
|
|
7
12
|
const DEFAULT_HEADERS: Record<string, string> = {
|
|
8
|
-
'Content-Type': 'application/json',
|
|
9
13
|
'Accept': 'application/json',
|
|
14
|
+
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',
|
|
15
|
+
'Cache-Control': 'no-cache',
|
|
16
|
+
'Pragma': 'no-cache',
|
|
17
|
+
'Origin': 'https://internxt.com',
|
|
18
|
+
'Referer': 'https://internxt.com/',
|
|
19
|
+
'User-Agent':
|
|
20
|
+
'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',
|
|
10
21
|
};
|
|
11
22
|
|
|
23
|
+
function jsonHeaders(extra?: Record<string, string>): Record<string, string> {
|
|
24
|
+
return { ...DEFAULT_HEADERS, 'Content-Type': 'application/json', ...extra };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function bearerHeaders(token: string, extra?: Record<string, string>): Record<string, string> {
|
|
28
|
+
return { ...jsonHeaders(extra), Authorization: `Bearer ${token}` };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function parseHydraMember(data: unknown): any[] {
|
|
32
|
+
if (Array.isArray(data)) return data;
|
|
33
|
+
if (data && typeof data === 'object') {
|
|
34
|
+
const o = data as Record<string, unknown>;
|
|
35
|
+
if (Array.isArray(o['hydra:member'])) return o['hydra:member'];
|
|
36
|
+
if (Array.isArray(o['data'])) return o['data'];
|
|
37
|
+
}
|
|
38
|
+
return [];
|
|
39
|
+
}
|
|
40
|
+
|
|
12
41
|
/**
|
|
13
42
|
* 生成随机字符串
|
|
14
43
|
*/
|
|
@@ -26,7 +55,7 @@ function randomString(length: number): string {
|
|
|
26
55
|
* API: GET /domains
|
|
27
56
|
*/
|
|
28
57
|
async function getDomains(): Promise<string[]> {
|
|
29
|
-
const response = await
|
|
58
|
+
const response = await fetchWithTimeout(`${BASE_URL}/domains?page=1`, {
|
|
30
59
|
method: 'GET',
|
|
31
60
|
headers: DEFAULT_HEADERS,
|
|
32
61
|
});
|
|
@@ -36,14 +65,11 @@ async function getDomains(): Promise<string[]> {
|
|
|
36
65
|
}
|
|
37
66
|
|
|
38
67
|
const data = await response.json();
|
|
39
|
-
|
|
40
|
-
* - Accept: application/ld+json → Hydra 格式 { "hydra:member": [...] }
|
|
41
|
-
* - Accept: application/json → 纯数组 [...]
|
|
42
|
-
*/
|
|
43
|
-
const members = Array.isArray(data) ? data : (data['hydra:member'] || []);
|
|
68
|
+
const members = parseHydraMember(data);
|
|
44
69
|
return members
|
|
45
|
-
.filter((d: any) => d.isActive)
|
|
46
|
-
.map((d: any) => d.domain)
|
|
70
|
+
.filter((d: any) => d && d.isActive !== false)
|
|
71
|
+
.map((d: any) => d.domain)
|
|
72
|
+
.filter(Boolean);
|
|
47
73
|
}
|
|
48
74
|
|
|
49
75
|
/**
|
|
@@ -51,7 +77,7 @@ async function getDomains(): Promise<string[]> {
|
|
|
51
77
|
* API: POST /accounts
|
|
52
78
|
*/
|
|
53
79
|
async function createAccount(address: string, password: string): Promise<any> {
|
|
54
|
-
const response = await
|
|
80
|
+
const response = await fetchWithTimeout(`${BASE_URL}/accounts`, {
|
|
55
81
|
method: 'POST',
|
|
56
82
|
headers: { ...DEFAULT_HEADERS, 'Content-Type': 'application/ld+json' },
|
|
57
83
|
body: JSON.stringify({ address, password }),
|
|
@@ -70,9 +96,9 @@ async function createAccount(address: string, password: string): Promise<any> {
|
|
|
70
96
|
* API: POST /token
|
|
71
97
|
*/
|
|
72
98
|
async function getToken(address: string, password: string): Promise<string> {
|
|
73
|
-
const response = await
|
|
99
|
+
const response = await fetchWithTimeout(`${BASE_URL}/token`, {
|
|
74
100
|
method: 'POST',
|
|
75
|
-
headers:
|
|
101
|
+
headers: jsonHeaders(),
|
|
76
102
|
body: JSON.stringify({ address, password }),
|
|
77
103
|
});
|
|
78
104
|
|
|
@@ -144,12 +170,9 @@ function flattenMessage(msg: any, recipientEmail: string): any {
|
|
|
144
170
|
*/
|
|
145
171
|
export async function getEmails(token: string, email: string): Promise<Email[]> {
|
|
146
172
|
// 1. 获取邮件列表
|
|
147
|
-
const listResponse = await
|
|
173
|
+
const listResponse = await fetchWithTimeout(`${BASE_URL}/messages?page=1`, {
|
|
148
174
|
method: 'GET',
|
|
149
|
-
headers:
|
|
150
|
-
...DEFAULT_HEADERS,
|
|
151
|
-
'Authorization': `Bearer ${token}`,
|
|
152
|
-
},
|
|
175
|
+
headers: bearerHeaders(token),
|
|
153
176
|
});
|
|
154
177
|
|
|
155
178
|
if (!listResponse.ok) {
|
|
@@ -157,8 +180,7 @@ export async function getEmails(token: string, email: string): Promise<Email[]>
|
|
|
157
180
|
}
|
|
158
181
|
|
|
159
182
|
const listData = await listResponse.json();
|
|
160
|
-
|
|
161
|
-
const messages = Array.isArray(listData) ? listData : (listData['hydra:member'] || []);
|
|
183
|
+
const messages = parseHydraMember(listData);
|
|
162
184
|
|
|
163
185
|
if (messages.length === 0) {
|
|
164
186
|
return [];
|
|
@@ -167,12 +189,9 @@ export async function getEmails(token: string, email: string): Promise<Email[]>
|
|
|
167
189
|
// 2. 并行获取每封邮件的详情(含 text/html/attachments)
|
|
168
190
|
const detailPromises = messages.map(async (msg: any) => {
|
|
169
191
|
try {
|
|
170
|
-
const detailResponse = await
|
|
192
|
+
const detailResponse = await fetchWithTimeout(`${BASE_URL}/messages/${msg.id}`, {
|
|
171
193
|
method: 'GET',
|
|
172
|
-
headers:
|
|
173
|
-
...DEFAULT_HEADERS,
|
|
174
|
-
'Authorization': `Bearer ${token}`,
|
|
175
|
-
},
|
|
194
|
+
headers: bearerHeaders(token),
|
|
176
195
|
});
|
|
177
196
|
|
|
178
197
|
if (!detailResponse.ok) {
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { InternalEmailInfo, Email, Channel } from '../types';
|
|
13
|
+
import { fetchWithTimeout } from '../retry';
|
|
13
14
|
|
|
14
15
|
const CHANNEL: Channel = 'maildrop';
|
|
15
16
|
const GRAPHQL_URL = 'https://api.maildrop.cc/graphql';
|
|
@@ -113,7 +114,7 @@ async function graphqlRequest(
|
|
|
113
114
|
query: string,
|
|
114
115
|
variables: Record<string, string> = {},
|
|
115
116
|
): Promise<any> {
|
|
116
|
-
const response = await
|
|
117
|
+
const response = await fetchWithTimeout(GRAPHQL_URL, {
|
|
117
118
|
method: 'POST',
|
|
118
119
|
headers: {
|
|
119
120
|
'Content-Type': 'application/json',
|
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
import { InternalEmailInfo, Email, Channel } from '../types';
|
|
2
|
+
import { normalizeEmail } from '../normalize';
|
|
3
|
+
import { fetchWithTimeout } from '../retry';
|
|
4
|
+
|
|
5
|
+
const CHANNEL: Channel = 'smail-pw';
|
|
6
|
+
const ROOT_DATA_URL = 'https://smail.pw/_root.data';
|
|
7
|
+
|
|
8
|
+
const DEFAULT_HEADERS: Record<string, string> = {
|
|
9
|
+
Accept: '*/*',
|
|
10
|
+
'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',
|
|
11
|
+
'cache-control': 'no-cache',
|
|
12
|
+
dnt: '1',
|
|
13
|
+
origin: 'https://smail.pw',
|
|
14
|
+
pragma: 'no-cache',
|
|
15
|
+
referer: 'https://smail.pw/',
|
|
16
|
+
'sec-ch-ua':
|
|
17
|
+
'"Chromium";v="146", "Not-A.Brand";v="24", "Microsoft Edge";v="146"',
|
|
18
|
+
'sec-ch-ua-mobile': '?0',
|
|
19
|
+
'sec-ch-ua-platform': '"Windows"',
|
|
20
|
+
'sec-fetch-dest': 'empty',
|
|
21
|
+
'sec-fetch-mode': 'cors',
|
|
22
|
+
'sec-fetch-site': 'same-origin',
|
|
23
|
+
'User-Agent':
|
|
24
|
+
'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',
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/** Flight/RSC 根为数组时,按出现顺序展开嵌套数组,保留标量与对象(不展开对象键),用于解析交错键值 */
|
|
28
|
+
function flightLinearLeaves(node: unknown): unknown[] {
|
|
29
|
+
if (!Array.isArray(node)) {
|
|
30
|
+
return [];
|
|
31
|
+
}
|
|
32
|
+
const out: unknown[] = [];
|
|
33
|
+
for (const el of node) {
|
|
34
|
+
if (Array.isArray(el)) {
|
|
35
|
+
out.push(...flightLinearLeaves(el));
|
|
36
|
+
} else {
|
|
37
|
+
out.push(el);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return out;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const MAIL_KV_KEYS = new Set(['id', 'to_address', 'from_name', 'from_address']);
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* 在线性序列中查找 ... "subject", <str>, "time", <num>,再向前成对读取已知字段。
|
|
47
|
+
* 比纯正则更耐受 emails 与 id 之间的 {"_23":4,...} 引用块。
|
|
48
|
+
*/
|
|
49
|
+
function parseSmailEmailsFromLinear(
|
|
50
|
+
leaves: unknown[],
|
|
51
|
+
recipientEmail: string,
|
|
52
|
+
): any[] {
|
|
53
|
+
const mails: any[] = [];
|
|
54
|
+
for (let i = 0; i + 3 < leaves.length; i++) {
|
|
55
|
+
if (leaves[i] !== 'subject') {
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
const subj = leaves[i + 1];
|
|
59
|
+
if (typeof subj !== 'string') {
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
if (leaves[i + 2] !== 'time') {
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
const timeRaw = leaves[i + 3];
|
|
66
|
+
const timeMs =
|
|
67
|
+
typeof timeRaw === 'number'
|
|
68
|
+
? timeRaw
|
|
69
|
+
: typeof timeRaw === 'string' && /^\d+$/.test(timeRaw)
|
|
70
|
+
? parseInt(timeRaw, 10)
|
|
71
|
+
: NaN;
|
|
72
|
+
if (!Number.isFinite(timeMs)) {
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const fields: Record<string, string> = {};
|
|
77
|
+
for (let k = i - 2; k >= 1; k -= 2) {
|
|
78
|
+
const key = leaves[k];
|
|
79
|
+
const val = leaves[k + 1];
|
|
80
|
+
if (typeof key !== 'string' || typeof val !== 'string') {
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
if (MAIL_KV_KEYS.has(key)) {
|
|
84
|
+
fields[key] = val;
|
|
85
|
+
}
|
|
86
|
+
/* 未知键值对跳过(RSC 可能在 subject 前插入其它字符串字段) */
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
mails.push({
|
|
90
|
+
id: fields.id || '',
|
|
91
|
+
to_address: fields.to_address || recipientEmail,
|
|
92
|
+
from_name: fields.from_name || '',
|
|
93
|
+
from_address: fields.from_address || '',
|
|
94
|
+
subject: subj,
|
|
95
|
+
date: timeMs,
|
|
96
|
+
text: '',
|
|
97
|
+
html: '',
|
|
98
|
+
attachments: [],
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
return mails;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function extractSessionCookie(response: Response): string {
|
|
105
|
+
const h = response.headers as Headers & { getSetCookie?: () => string[] };
|
|
106
|
+
if (typeof h.getSetCookie === 'function') {
|
|
107
|
+
for (const line of h.getSetCookie()) {
|
|
108
|
+
const m = line.match(/^__session=([^;]+)/);
|
|
109
|
+
if (m) return `__session=${m[1]}`;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
const single = response.headers.get('set-cookie');
|
|
113
|
+
if (single) {
|
|
114
|
+
const m = single.match(/__session=([^;]+)/);
|
|
115
|
+
if (m) return `__session=${m[1]}`;
|
|
116
|
+
}
|
|
117
|
+
return '';
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** RSC/Flight 风格 JSON 文本中提取 @smail.pw 收件地址 */
|
|
121
|
+
function extractInboxEmail(text: string): string | null {
|
|
122
|
+
const quoted = text.match(/"([a-z0-9][a-z0-9.-]*@smail\.pw)"/i);
|
|
123
|
+
if (quoted) return quoted[1];
|
|
124
|
+
const plain = text.match(/\b([a-z0-9][a-z0-9.-]*@smail\.pw)\b/i);
|
|
125
|
+
return plain ? plain[1] : null;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** 从响应体解析邮件条目(正则路径,与早期 curl 样本一致) */
|
|
129
|
+
function parseSmailEmailsRegex(text: string, recipientEmail: string): any[] {
|
|
130
|
+
const out: any[] = [];
|
|
131
|
+
const re =
|
|
132
|
+
/"id","([^"]+)","to_address","([^"]*)","from_name","([^"]*)","from_address","([^"]*)","subject","([^"]*)","time",(\d+)/g;
|
|
133
|
+
let m: RegExpExecArray | null;
|
|
134
|
+
while ((m = re.exec(text)) !== null) {
|
|
135
|
+
out.push({
|
|
136
|
+
id: m[1],
|
|
137
|
+
to_address: m[2] || recipientEmail,
|
|
138
|
+
from_name: m[3],
|
|
139
|
+
from_address: m[4],
|
|
140
|
+
subject: m[5],
|
|
141
|
+
date: parseInt(m[6], 10),
|
|
142
|
+
text: '',
|
|
143
|
+
html: '',
|
|
144
|
+
attachments: [],
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
if (out.length > 0) {
|
|
148
|
+
return out;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const re2 =
|
|
152
|
+
/"id","([^"]+)","from_name","([^"]*)","from_address","([^"]*)","subject","([^"]*)","time",(\d+)/g;
|
|
153
|
+
while ((m = re2.exec(text)) !== null) {
|
|
154
|
+
out.push({
|
|
155
|
+
id: m[1],
|
|
156
|
+
to_address: recipientEmail,
|
|
157
|
+
from_name: m[2],
|
|
158
|
+
from_address: m[3],
|
|
159
|
+
subject: m[4],
|
|
160
|
+
date: parseInt(m[5], 10),
|
|
161
|
+
text: '',
|
|
162
|
+
html: '',
|
|
163
|
+
attachments: [],
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
return out;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function mergeMailsById(lists: any[][]): any[] {
|
|
170
|
+
const map = new Map<string, any>();
|
|
171
|
+
let anon = 0;
|
|
172
|
+
for (const list of lists) {
|
|
173
|
+
for (const mail of list) {
|
|
174
|
+
let id = String(mail?.id || '');
|
|
175
|
+
if (!id) {
|
|
176
|
+
id = `__smail_${anon++}_${mail?.date ?? ''}_${String(mail?.subject ?? '').slice(0, 48)}`;
|
|
177
|
+
mail.id = id;
|
|
178
|
+
}
|
|
179
|
+
if (!map.has(id)) {
|
|
180
|
+
map.set(id, mail);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return [...map.values()];
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* 官方 loader 返回 D1 行:{ id, to_address, from_name, from_address, subject, time }(见 akazwz/smail app/types/email.ts)。
|
|
189
|
+
* Flight 序列化后多为普通 JSON 对象,而非 "id","…" 交错字符串元组,须在整棵树递归收集。
|
|
190
|
+
*/
|
|
191
|
+
function collectPlainRowEmails(root: unknown, recipientEmail: string): any[] {
|
|
192
|
+
const mails: any[] = [];
|
|
193
|
+
const seen = new WeakSet<object>();
|
|
194
|
+
|
|
195
|
+
function walk(node: unknown): void {
|
|
196
|
+
if (node === null || node === undefined) {
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
if (typeof node !== 'object') {
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
if (seen.has(node as object)) {
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
seen.add(node as object);
|
|
206
|
+
|
|
207
|
+
if (Array.isArray(node)) {
|
|
208
|
+
for (const el of node) {
|
|
209
|
+
walk(el);
|
|
210
|
+
}
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const o = node as Record<string, unknown>;
|
|
215
|
+
if (typeof o.subject === 'string') {
|
|
216
|
+
const tr = o.time;
|
|
217
|
+
const timeMs =
|
|
218
|
+
typeof tr === 'number' && Number.isFinite(tr)
|
|
219
|
+
? tr
|
|
220
|
+
: typeof tr === 'string' && /^\d+$/.test(tr)
|
|
221
|
+
? parseInt(tr, 10)
|
|
222
|
+
: NaN;
|
|
223
|
+
if (Number.isFinite(timeMs)) {
|
|
224
|
+
mails.push({
|
|
225
|
+
id: String(o.id ?? ''),
|
|
226
|
+
to_address: String(o.to_address ?? recipientEmail),
|
|
227
|
+
from_name: String(o.from_name ?? ''),
|
|
228
|
+
from_address: String(o.from_address ?? ''),
|
|
229
|
+
subject: o.subject,
|
|
230
|
+
date: timeMs,
|
|
231
|
+
text: '',
|
|
232
|
+
html: '',
|
|
233
|
+
attachments: [],
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
for (const v of Object.values(o)) {
|
|
239
|
+
if (v !== null && typeof v === 'object') {
|
|
240
|
+
walk(v);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
walk(root);
|
|
246
|
+
return mails;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Flight 风格 payload:根为数组,"addresses",[i] 表示 root[i] 为邮箱字符串;
|
|
251
|
+
* "emails",[a,b,...] 中每项为下标,指向 root[idx] 的邮件行(常为嵌套数组,内含 id/to_address/subject/time 键值序列)。
|
|
252
|
+
*/
|
|
253
|
+
function resolveFlightSlot(root: unknown[], idx: number, visited: Set<number>): unknown {
|
|
254
|
+
if (idx < 0 || idx >= root.length || visited.has(idx)) {
|
|
255
|
+
return undefined;
|
|
256
|
+
}
|
|
257
|
+
visited.add(idx);
|
|
258
|
+
const val = root[idx];
|
|
259
|
+
if (typeof val === 'number' && Number.isInteger(val) && val >= 0 && val < root.length) {
|
|
260
|
+
return resolveFlightSlot(root, val, visited);
|
|
261
|
+
}
|
|
262
|
+
return val;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function flattenFlightIndices(refs: unknown[]): number[] {
|
|
266
|
+
const out: number[] = [];
|
|
267
|
+
for (const r of refs) {
|
|
268
|
+
if (typeof r === 'number' && Number.isInteger(r)) {
|
|
269
|
+
out.push(r);
|
|
270
|
+
} else if (typeof r === 'string' && /^\d+$/.test(r)) {
|
|
271
|
+
out.push(parseInt(r, 10));
|
|
272
|
+
} else if (Array.isArray(r)) {
|
|
273
|
+
for (const x of r) {
|
|
274
|
+
if (typeof x === 'number' && Number.isInteger(x)) {
|
|
275
|
+
out.push(x);
|
|
276
|
+
} else if (typeof x === 'string' && /^\d+$/.test(x)) {
|
|
277
|
+
out.push(parseInt(x, 10));
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
return out;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function parseSmailEmailsFromFlightRoot(root: unknown[], recipientEmail: string): any[] {
|
|
286
|
+
const mails: any[] = [];
|
|
287
|
+
for (let i = 0; i < root.length - 1; i++) {
|
|
288
|
+
if (root[i] !== 'emails') {
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
const refs = root[i + 1];
|
|
292
|
+
if (!Array.isArray(refs)) {
|
|
293
|
+
break;
|
|
294
|
+
}
|
|
295
|
+
for (const r of flattenFlightIndices(refs)) {
|
|
296
|
+
const node = resolveFlightSlot(root, r, new Set());
|
|
297
|
+
if (Array.isArray(node)) {
|
|
298
|
+
const leaves = flightLinearLeaves(node);
|
|
299
|
+
mails.push(...parseSmailEmailsFromLinear(leaves, recipientEmail));
|
|
300
|
+
} else if (node !== null && typeof node === 'object') {
|
|
301
|
+
mails.push(...collectPlainRowEmails(node, recipientEmail));
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
break;
|
|
305
|
+
}
|
|
306
|
+
return mails;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function parseSmailEmailsFromPayload(text: string, recipientEmail: string): any[] {
|
|
310
|
+
const regexMails = parseSmailEmailsRegex(text, recipientEmail);
|
|
311
|
+
let linearMails: any[] = [];
|
|
312
|
+
let flightMails: any[] = [];
|
|
313
|
+
let plainMails: any[] = [];
|
|
314
|
+
try {
|
|
315
|
+
const root = JSON.parse(text) as unknown;
|
|
316
|
+
plainMails = collectPlainRowEmails(root, recipientEmail);
|
|
317
|
+
if (Array.isArray(root)) {
|
|
318
|
+
flightMails = parseSmailEmailsFromFlightRoot(root, recipientEmail);
|
|
319
|
+
const leaves = flightLinearLeaves(root);
|
|
320
|
+
linearMails = parseSmailEmailsFromLinear(leaves, recipientEmail);
|
|
321
|
+
}
|
|
322
|
+
} catch {
|
|
323
|
+
/* 非 JSON 或结构异常时仅用正则 */
|
|
324
|
+
}
|
|
325
|
+
return mergeMailsById([regexMails, linearMails, flightMails, plainMails]);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* POST intent=generate,保存 Set-Cookie __session,从 RSC 响应中解析邮箱
|
|
330
|
+
*/
|
|
331
|
+
export async function generateEmail(): Promise<InternalEmailInfo> {
|
|
332
|
+
const response = await fetchWithTimeout(ROOT_DATA_URL, {
|
|
333
|
+
method: 'POST',
|
|
334
|
+
headers: {
|
|
335
|
+
...DEFAULT_HEADERS,
|
|
336
|
+
'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
|
|
337
|
+
},
|
|
338
|
+
body: 'intent=generate',
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
if (!response.ok) {
|
|
342
|
+
throw new Error(`smail.pw generate failed: ${response.status}`);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const cookie = extractSessionCookie(response);
|
|
346
|
+
if (!cookie) {
|
|
347
|
+
throw new Error('Failed to extract __session cookie');
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const text = await response.text();
|
|
351
|
+
const email = extractInboxEmail(text);
|
|
352
|
+
if (!email) {
|
|
353
|
+
throw new Error('Failed to parse inbox address from smail.pw response');
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
return {
|
|
357
|
+
channel: CHANNEL,
|
|
358
|
+
email,
|
|
359
|
+
token: cookie,
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* GET _root.data,携带 __session Cookie,解析邮件列表
|
|
365
|
+
*/
|
|
366
|
+
export async function getEmails(token: string, email: string): Promise<Email[]> {
|
|
367
|
+
const response = await fetchWithTimeout(ROOT_DATA_URL, {
|
|
368
|
+
method: 'GET',
|
|
369
|
+
headers: {
|
|
370
|
+
...DEFAULT_HEADERS,
|
|
371
|
+
Cookie: token,
|
|
372
|
+
},
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
if (!response.ok) {
|
|
376
|
+
throw new Error(`smail.pw get emails failed: ${response.status}`);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
const text = await response.text();
|
|
380
|
+
const rawList = parseSmailEmailsFromPayload(text, email);
|
|
381
|
+
return rawList.map((raw) => normalizeEmail(raw, email));
|
|
382
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { InternalEmailInfo, Email, Channel } from '../types';
|
|
2
2
|
import { normalizeEmail } from '../normalize';
|
|
3
|
+
import { fetchWithTimeout } from '../retry';
|
|
3
4
|
|
|
4
5
|
const CHANNEL: Channel = 'temp-mail-io';
|
|
5
6
|
const BASE_URL = 'https://api.internal.temp-mail.io/api/v3';
|
|
@@ -18,7 +19,7 @@ async function fetchCorsHeader(): Promise<string> {
|
|
|
18
19
|
if (cachedCorsHeader) return cachedCorsHeader;
|
|
19
20
|
|
|
20
21
|
try {
|
|
21
|
-
const response = await
|
|
22
|
+
const response = await fetchWithTimeout(PAGE_URL, {
|
|
22
23
|
headers: {
|
|
23
24
|
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36',
|
|
24
25
|
},
|
|
@@ -61,7 +62,7 @@ async function getApiHeaders(): Promise<Record<string, string>> {
|
|
|
61
62
|
*/
|
|
62
63
|
export async function generateEmail(): Promise<InternalEmailInfo> {
|
|
63
64
|
const headers = await getApiHeaders();
|
|
64
|
-
const response = await
|
|
65
|
+
const response = await fetchWithTimeout(`${BASE_URL}/email/new`, {
|
|
65
66
|
method: 'POST',
|
|
66
67
|
headers,
|
|
67
68
|
body: JSON.stringify({ min_name_length: 10, max_name_length: 10 }),
|
|
@@ -91,7 +92,7 @@ export async function generateEmail(): Promise<InternalEmailInfo> {
|
|
|
91
92
|
*/
|
|
92
93
|
export async function getEmails(email: string): Promise<Email[]> {
|
|
93
94
|
const headers = await getApiHeaders();
|
|
94
|
-
const response = await
|
|
95
|
+
const response = await fetchWithTimeout(`${BASE_URL}/email/${email}/messages`, {
|
|
95
96
|
method: 'GET',
|
|
96
97
|
headers,
|
|
97
98
|
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { InternalEmailInfo, Email, Channel } from '../types';
|
|
2
2
|
import { normalizeEmail } from '../normalize';
|
|
3
|
+
import { fetchWithTimeout } from '../retry';
|
|
3
4
|
|
|
4
5
|
const CHANNEL: Channel = 'tempmail-lol';
|
|
5
6
|
const BASE_URL = 'https://api.tempmail.lol/v2';
|
|
@@ -15,7 +16,7 @@ const DEFAULT_HEADERS = {
|
|
|
15
16
|
};
|
|
16
17
|
|
|
17
18
|
export async function generateEmail(domain: string | null = null): Promise<InternalEmailInfo> {
|
|
18
|
-
const response = await
|
|
19
|
+
const response = await fetchWithTimeout(`${BASE_URL}/inbox/create`, {
|
|
19
20
|
method: 'POST',
|
|
20
21
|
headers: DEFAULT_HEADERS,
|
|
21
22
|
body: JSON.stringify({ domain, captcha: null }),
|
|
@@ -39,7 +40,7 @@ export async function generateEmail(domain: string | null = null): Promise<Inter
|
|
|
39
40
|
}
|
|
40
41
|
|
|
41
42
|
export async function getEmails(token: string, recipientEmail: string = ''): Promise<Email[]> {
|
|
42
|
-
const response = await
|
|
43
|
+
const response = await fetchWithTimeout(`${BASE_URL}/inbox?token=${encodeURIComponent(token)}`, {
|
|
43
44
|
method: 'GET',
|
|
44
45
|
headers: DEFAULT_HEADERS,
|
|
45
46
|
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { InternalEmailInfo, Email, Channel } from '../types';
|
|
2
2
|
import { normalizeEmail } from '../normalize';
|
|
3
|
+
import { fetchWithTimeout } from '../retry';
|
|
3
4
|
|
|
4
5
|
const CHANNEL: Channel = 'tempmail';
|
|
5
6
|
const BASE_URL = 'https://api.tempmail.ing/api';
|
|
@@ -15,7 +16,7 @@ const DEFAULT_HEADERS = {
|
|
|
15
16
|
};
|
|
16
17
|
|
|
17
18
|
export async function generateEmail(duration: number = 30): Promise<InternalEmailInfo> {
|
|
18
|
-
const response = await
|
|
19
|
+
const response = await fetchWithTimeout(`${BASE_URL}/generate`, {
|
|
19
20
|
method: 'POST',
|
|
20
21
|
headers: DEFAULT_HEADERS,
|
|
21
22
|
body: JSON.stringify({ duration }),
|
|
@@ -41,7 +42,7 @@ export async function generateEmail(duration: number = 30): Promise<InternalEmai
|
|
|
41
42
|
|
|
42
43
|
export async function getEmails(email: string): Promise<Email[]> {
|
|
43
44
|
const encodedEmail = encodeURIComponent(email);
|
|
44
|
-
const response = await
|
|
45
|
+
const response = await fetchWithTimeout(`${BASE_URL}/emails/${encodedEmail}`, {
|
|
45
46
|
method: 'GET',
|
|
46
47
|
headers: DEFAULT_HEADERS,
|
|
47
48
|
});
|