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
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { InternalEmailInfo, Email, Channel } from '../types';
|
|
2
|
+
import { normalizeEmail } from '../normalize';
|
|
3
|
+
import { fetchWithTimeout } from '../retry';
|
|
4
|
+
|
|
5
|
+
const CHANNEL: Channel = 'moakt';
|
|
6
|
+
const ORIGIN = 'https://www.moakt.com';
|
|
7
|
+
const TOK_PREFIX = 'mok1:';
|
|
8
|
+
|
|
9
|
+
const EMAIL_DIV_RE = /<div\s+id="email-address"\s*>([^<]+)<\/div>/is;
|
|
10
|
+
const HREF_EMAIL_RE =
|
|
11
|
+
/href="(\/[^"]+\/email\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})"/gi;
|
|
12
|
+
const TITLE_RE = /<li\s+class="title"\s*>([^<]*)<\/li>/is;
|
|
13
|
+
const DATE_RE = /<li\s+class="date"[^>]*>[\s\S]*?<span[^>]*>([^<]+)<\/span>/is;
|
|
14
|
+
const SENDER_RE = /<li\s+class="sender"[^>]*>[\s\S]*?<span[^>]*>([\s\S]*?)<\/span>\s*<\/li>/is;
|
|
15
|
+
const BODY_RE = /<div\s+class="email-body"\s*>([\s\S]*?)<\/div>/is;
|
|
16
|
+
const FROM_ADDR_RE = /<([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})>/;
|
|
17
|
+
const TAG_RE = /<[^>]+>/g;
|
|
18
|
+
|
|
19
|
+
function setCookieLines(headers: Headers): string[] {
|
|
20
|
+
const h = headers as Headers & { getSetCookie?: () => string[] };
|
|
21
|
+
if (typeof h.getSetCookie === 'function') {
|
|
22
|
+
return h.getSetCookie();
|
|
23
|
+
}
|
|
24
|
+
const one = headers.get('set-cookie');
|
|
25
|
+
return one ? [one] : [];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function localeFromDomain(domain?: string | null): string {
|
|
29
|
+
const s = String(domain ?? '').trim();
|
|
30
|
+
if (!s || /[/?#\\]/.test(s)) return 'zh';
|
|
31
|
+
return s;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function parseCookieMap(hdr: string): Map<string, string> {
|
|
35
|
+
const m = new Map<string, string>();
|
|
36
|
+
for (const part of hdr.split(';')) {
|
|
37
|
+
const p = part.trim();
|
|
38
|
+
if (!p || !p.includes('=')) continue;
|
|
39
|
+
const i = p.indexOf('=');
|
|
40
|
+
const k = p.slice(0, i).trim();
|
|
41
|
+
const v = p.slice(i + 1).trim();
|
|
42
|
+
if (k) m.set(k, v);
|
|
43
|
+
}
|
|
44
|
+
return m;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function mergeCookieHeader(prev: string, headers: Headers): string {
|
|
48
|
+
const m = parseCookieMap(prev);
|
|
49
|
+
for (const line of setCookieLines(headers)) {
|
|
50
|
+
const first = line.split(';')[0] ?? '';
|
|
51
|
+
const i = first.indexOf('=');
|
|
52
|
+
if (i <= 0) continue;
|
|
53
|
+
const k = first.slice(0, i).trim();
|
|
54
|
+
const v = first.slice(i + 1).trim();
|
|
55
|
+
if (k) m.set(k, v);
|
|
56
|
+
}
|
|
57
|
+
return [...m.entries()]
|
|
58
|
+
.sort((a, b) => a[0].localeCompare(b[0]))
|
|
59
|
+
.map(([k, v]) => `${k}=${v}`)
|
|
60
|
+
.join('; ');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function lightUnescape(s: string): string {
|
|
64
|
+
return s
|
|
65
|
+
.replace(/&/g, '\u0000')
|
|
66
|
+
.replace(/"/g, '"')
|
|
67
|
+
.replace(/"/g, '"')
|
|
68
|
+
.replace(/'/g, "'")
|
|
69
|
+
.replace(/</g, '<')
|
|
70
|
+
.replace(/>/g, '>')
|
|
71
|
+
.replace(/\u0000/g, '&');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function encSess(l: string, c: string): string {
|
|
75
|
+
const raw = JSON.stringify({ l, c });
|
|
76
|
+
return TOK_PREFIX + Buffer.from(raw, 'utf8').toString('base64');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function decSess(tok: string): { l: string; c: string } {
|
|
80
|
+
if (!tok.startsWith(TOK_PREFIX)) {
|
|
81
|
+
throw new Error('moakt: invalid session token');
|
|
82
|
+
}
|
|
83
|
+
let raw: string;
|
|
84
|
+
try {
|
|
85
|
+
raw = Buffer.from(tok.slice(TOK_PREFIX.length), 'base64').toString('utf8');
|
|
86
|
+
} catch {
|
|
87
|
+
throw new Error('moakt: invalid session token');
|
|
88
|
+
}
|
|
89
|
+
const o = JSON.parse(raw) as { l?: string; c?: string };
|
|
90
|
+
const l = (o.l ?? '').trim();
|
|
91
|
+
const c = (o.c ?? '').trim();
|
|
92
|
+
if (!l || !c) throw new Error('moakt: invalid session token');
|
|
93
|
+
return { l, c };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function pageHeaders(referer: string): Record<string, string> {
|
|
97
|
+
return {
|
|
98
|
+
Accept:
|
|
99
|
+
'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8',
|
|
100
|
+
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',
|
|
101
|
+
'Cache-Control': 'no-cache',
|
|
102
|
+
DNT: '1',
|
|
103
|
+
Pragma: 'no-cache',
|
|
104
|
+
Referer: referer,
|
|
105
|
+
'Upgrade-Insecure-Requests': '1',
|
|
106
|
+
'User-Agent':
|
|
107
|
+
'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',
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function parseInboxEmail(html: string): string {
|
|
112
|
+
const m = EMAIL_DIV_RE.exec(html);
|
|
113
|
+
if (!m?.[1]) throw new Error('moakt: email-address not found');
|
|
114
|
+
const addr = lightUnescape(m[1].trim());
|
|
115
|
+
if (!addr) throw new Error('moakt: empty email-address');
|
|
116
|
+
return addr;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function stripTags(s: string): string {
|
|
120
|
+
return s.replace(TAG_RE, ' ').replace(/\s+/g, ' ').trim();
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function listMailIds(html: string): string[] {
|
|
124
|
+
const seen = new Set<string>();
|
|
125
|
+
const out: string[] = [];
|
|
126
|
+
HREF_EMAIL_RE.lastIndex = 0;
|
|
127
|
+
let cap: RegExpExecArray | null;
|
|
128
|
+
while ((cap = HREF_EMAIL_RE.exec(html)) !== null) {
|
|
129
|
+
const path = cap[1];
|
|
130
|
+
if (path.includes('/delete')) continue;
|
|
131
|
+
const id = path.split('/').pop() ?? '';
|
|
132
|
+
if (id.length === 36 && !seen.has(id)) {
|
|
133
|
+
seen.add(id);
|
|
134
|
+
out.push(id);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return out;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function parseDetail(page: string, id: string, recipient: string): Record<string, unknown> {
|
|
141
|
+
let fromS = '';
|
|
142
|
+
const sm = SENDER_RE.exec(page);
|
|
143
|
+
if (sm?.[1]) {
|
|
144
|
+
const inner = lightUnescape(sm[1]);
|
|
145
|
+
fromS = stripTags(inner);
|
|
146
|
+
const em = FROM_ADDR_RE.exec(inner);
|
|
147
|
+
if (em?.[1]) fromS = em[1].trim();
|
|
148
|
+
}
|
|
149
|
+
let subject = '';
|
|
150
|
+
const tm = TITLE_RE.exec(page);
|
|
151
|
+
if (tm?.[1]) subject = lightUnescape(tm[1].trim());
|
|
152
|
+
let date = '';
|
|
153
|
+
const dm = DATE_RE.exec(page);
|
|
154
|
+
if (dm?.[1]) date = lightUnescape(dm[1].trim());
|
|
155
|
+
let htmlBody = '';
|
|
156
|
+
const bm = BODY_RE.exec(page);
|
|
157
|
+
if (bm?.[1]) htmlBody = bm[1].trim();
|
|
158
|
+
return {
|
|
159
|
+
id,
|
|
160
|
+
to: recipient,
|
|
161
|
+
from: fromS,
|
|
162
|
+
subject,
|
|
163
|
+
date,
|
|
164
|
+
html: htmlBody,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export async function generateEmail(domain?: string | null): Promise<InternalEmailInfo> {
|
|
169
|
+
const loc = localeFromDomain(domain);
|
|
170
|
+
const base = `${ORIGIN}/${loc}`;
|
|
171
|
+
const inbox = `${base}/inbox`;
|
|
172
|
+
|
|
173
|
+
const r1 = await fetchWithTimeout(base, { headers: pageHeaders(base) });
|
|
174
|
+
if (!r1.ok) throw new Error(`moakt generate: ${r1.status}`);
|
|
175
|
+
let cookieHdr = mergeCookieHeader('', r1.headers);
|
|
176
|
+
|
|
177
|
+
const r2 = await fetchWithTimeout(inbox, {
|
|
178
|
+
headers: { ...pageHeaders(base), Cookie: cookieHdr },
|
|
179
|
+
});
|
|
180
|
+
if (!r2.ok) throw new Error(`moakt inbox: ${r2.status}`);
|
|
181
|
+
cookieHdr = mergeCookieHeader(cookieHdr, r2.headers);
|
|
182
|
+
const html = await r2.text();
|
|
183
|
+
|
|
184
|
+
const email = parseInboxEmail(html);
|
|
185
|
+
if (!parseCookieMap(cookieHdr).has('tm_session')) {
|
|
186
|
+
throw new Error('moakt: missing tm_session cookie');
|
|
187
|
+
}
|
|
188
|
+
const token = encSess(loc, cookieHdr);
|
|
189
|
+
return { channel: CHANNEL, email, token };
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export async function getEmails(email: string, token: string): Promise<Email[]> {
|
|
193
|
+
const { l: loc, c: cookieHdr } = decSess(token);
|
|
194
|
+
const inbox = `${ORIGIN}/${loc}/inbox`;
|
|
195
|
+
const baseRef = `${ORIGIN}/${loc}`;
|
|
196
|
+
|
|
197
|
+
const r = await fetchWithTimeout(inbox, {
|
|
198
|
+
headers: { ...pageHeaders(baseRef), Cookie: cookieHdr },
|
|
199
|
+
});
|
|
200
|
+
if (!r.ok) throw new Error(`moakt inbox: ${r.status}`);
|
|
201
|
+
const inboxHtml = await r.text();
|
|
202
|
+
const ids = listMailIds(inboxHtml);
|
|
203
|
+
const out: Email[] = [];
|
|
204
|
+
for (const id of ids) {
|
|
205
|
+
const detail = `${ORIGIN}/${loc}/email/${id}/html`;
|
|
206
|
+
const refer = `${ORIGIN}/${loc}/email/${id}`;
|
|
207
|
+
const rd = await fetchWithTimeout(detail, {
|
|
208
|
+
headers: { ...pageHeaders(refer), Cookie: cookieHdr },
|
|
209
|
+
});
|
|
210
|
+
if (!rd.ok) continue;
|
|
211
|
+
const page = await rd.text();
|
|
212
|
+
out.push(normalizeEmail(parseDetail(page, id, email), email));
|
|
213
|
+
}
|
|
214
|
+
return out;
|
|
215
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { InternalEmailInfo, Email, Channel } from '../types';
|
|
2
|
+
import { normalizeEmail } from '../normalize';
|
|
3
|
+
import { fetchWithTimeout } from '../retry';
|
|
4
|
+
|
|
5
|
+
const CHANNEL: Channel = 'ta-easy';
|
|
6
|
+
const API_BASE = 'https://api-endpoint.ta-easy.com';
|
|
7
|
+
const ORIGIN = 'https://www.ta-easy.com';
|
|
8
|
+
|
|
9
|
+
function browserHeaders(): Record<string, string> {
|
|
10
|
+
return {
|
|
11
|
+
Accept: 'application/json',
|
|
12
|
+
'User-Agent':
|
|
13
|
+
'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',
|
|
14
|
+
origin: ORIGIN,
|
|
15
|
+
referer: `${ORIGIN}/`,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** POST /temp-email/address/new(空 body) */
|
|
20
|
+
export async function generateEmail(): Promise<InternalEmailInfo> {
|
|
21
|
+
const res = await fetchWithTimeout(`${API_BASE}/temp-email/address/new`, {
|
|
22
|
+
method: 'POST',
|
|
23
|
+
headers: { ...browserHeaders(), 'Content-Length': '0' },
|
|
24
|
+
});
|
|
25
|
+
if (!res.ok) {
|
|
26
|
+
throw new Error(`ta-easy generate: ${res.status}`);
|
|
27
|
+
}
|
|
28
|
+
const data = await res.json();
|
|
29
|
+
if (data.status !== 'success' || !data.address || !data.token) {
|
|
30
|
+
throw new Error(data.message ? `ta-easy: ${data.message}` : 'ta-easy: create failed');
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
channel: CHANNEL,
|
|
34
|
+
email: data.address as string,
|
|
35
|
+
token: data.token as string,
|
|
36
|
+
expiresAt: typeof data.expiresAt === 'number' ? data.expiresAt : undefined,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** POST /temp-email/inbox/list */
|
|
41
|
+
export async function getEmails(email: string, token: string): Promise<Email[]> {
|
|
42
|
+
const res = await fetchWithTimeout(`${API_BASE}/temp-email/inbox/list`, {
|
|
43
|
+
method: 'POST',
|
|
44
|
+
headers: { ...browserHeaders(), 'Content-Type': 'application/json' },
|
|
45
|
+
body: JSON.stringify({ token, email }),
|
|
46
|
+
});
|
|
47
|
+
if (!res.ok) {
|
|
48
|
+
throw new Error(`ta-easy inbox: ${res.status}`);
|
|
49
|
+
}
|
|
50
|
+
const data = await res.json();
|
|
51
|
+
if (data.status !== 'success') {
|
|
52
|
+
throw new Error(data.message ? `ta-easy: ${data.message}` : 'ta-easy: inbox failed');
|
|
53
|
+
}
|
|
54
|
+
const list = Array.isArray(data.data) ? data.data : [];
|
|
55
|
+
return list.map((raw: any) => normalizeEmail(raw, email));
|
|
56
|
+
}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* tempmailg.com:无 Cookie 罐 GET /public/{locale} 拿会话,POST /public/get_messages 建邮与收信。
|
|
3
|
+
* Token 为 tmg1: + base64(JSON{locale,cookieHdr,csrf})。
|
|
4
|
+
*/
|
|
5
|
+
import { InternalEmailInfo, Email, Channel } from '../types';
|
|
6
|
+
import { normalizeEmail } from '../normalize';
|
|
7
|
+
import { fetchWithTimeout } from '../retry';
|
|
8
|
+
|
|
9
|
+
const CHANNEL: Channel = 'tempmailg';
|
|
10
|
+
const ORIGIN = 'https://tempmailg.com';
|
|
11
|
+
const TOK_PREFIX = 'tmg1:';
|
|
12
|
+
|
|
13
|
+
const CSRF_META_RE = /<meta\s+name="csrf-token"\s+content="([^"]+)"/i;
|
|
14
|
+
|
|
15
|
+
const BROWSER_UA =
|
|
16
|
+
'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';
|
|
17
|
+
|
|
18
|
+
function localeFromDomain(domain?: string | null): string {
|
|
19
|
+
const s = String(domain ?? '').trim();
|
|
20
|
+
if (!s || /[/?#\\]/.test(s)) return 'zh';
|
|
21
|
+
return s;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function setCookieLines(headers: Headers): string[] {
|
|
25
|
+
const h = headers as Headers & { getSetCookie?: () => string[] };
|
|
26
|
+
if (typeof h.getSetCookie === 'function') {
|
|
27
|
+
return h.getSetCookie();
|
|
28
|
+
}
|
|
29
|
+
const one = headers.get('set-cookie');
|
|
30
|
+
return one ? [one] : [];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function cookieMap(cookieHdr: string): Map<string, string> {
|
|
34
|
+
const m = new Map<string, string>();
|
|
35
|
+
for (const part of cookieHdr.split(';')) {
|
|
36
|
+
const p = part.trim();
|
|
37
|
+
if (!p) continue;
|
|
38
|
+
const i = p.indexOf('=');
|
|
39
|
+
if (i <= 0 || i >= p.length - 1) continue;
|
|
40
|
+
const k = p.slice(0, i).trim();
|
|
41
|
+
const v = p.slice(i + 1).trim();
|
|
42
|
+
if (k) m.set(k, v);
|
|
43
|
+
}
|
|
44
|
+
return m;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function mergeCookies(prev: string, setCookieLinesFromResp: string[]): string {
|
|
48
|
+
const m = cookieMap(prev);
|
|
49
|
+
for (const line of setCookieLinesFromResp) {
|
|
50
|
+
const nv = line.split(';')[0]?.trim();
|
|
51
|
+
if (!nv) continue;
|
|
52
|
+
const i = nv.indexOf('=');
|
|
53
|
+
if (i <= 0) continue;
|
|
54
|
+
const k = nv.slice(0, i).trim();
|
|
55
|
+
const v = nv.slice(i + 1).trim();
|
|
56
|
+
if (k) m.set(k, v);
|
|
57
|
+
}
|
|
58
|
+
return [...m.entries()]
|
|
59
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
60
|
+
.map(([k, v]) => `${k}=${v}`)
|
|
61
|
+
.join('; ');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function xsrfFromCookies(cookieHdr: string): string {
|
|
65
|
+
const m = cookieMap(cookieHdr);
|
|
66
|
+
for (const name of ['XSRF-TOKEN', 'xsrf-token']) {
|
|
67
|
+
const v = m.get(name);
|
|
68
|
+
if (v) return v;
|
|
69
|
+
}
|
|
70
|
+
for (const [k, v] of m) {
|
|
71
|
+
if (k.toLowerCase() === 'xsrf-token' && v) return v;
|
|
72
|
+
}
|
|
73
|
+
return '';
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function parseCsrf(html: string): string {
|
|
77
|
+
const m = html.match(CSRF_META_RE);
|
|
78
|
+
const t = m?.[1]?.trim() ?? '';
|
|
79
|
+
if (!t) throw new Error('tempmailg: csrf-token not found in page');
|
|
80
|
+
return t;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function b64EncodeJson(obj: { l: string; c: string; s: string }): string {
|
|
84
|
+
const json = JSON.stringify(obj);
|
|
85
|
+
if (typeof Buffer !== 'undefined') {
|
|
86
|
+
return TOK_PREFIX + Buffer.from(json, 'utf8').toString('base64');
|
|
87
|
+
}
|
|
88
|
+
return TOK_PREFIX + btoa(unescape(encodeURIComponent(json)));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function decodeToken(tok: string): { l: string; c: string; s: string } {
|
|
92
|
+
if (!tok.startsWith(TOK_PREFIX)) {
|
|
93
|
+
throw new Error('tempmailg: invalid session token');
|
|
94
|
+
}
|
|
95
|
+
const raw = tok.slice(TOK_PREFIX.length);
|
|
96
|
+
let json: string;
|
|
97
|
+
if (typeof Buffer !== 'undefined') {
|
|
98
|
+
json = Buffer.from(raw, 'base64').toString('utf8');
|
|
99
|
+
} else {
|
|
100
|
+
json = decodeURIComponent(escape(atob(raw)));
|
|
101
|
+
}
|
|
102
|
+
const o = JSON.parse(json) as { l?: string; c?: string; s?: string };
|
|
103
|
+
if (!o.c || !o.s) throw new Error('tempmailg: invalid session token');
|
|
104
|
+
return { l: o.l || 'zh', c: o.c, s: o.s };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function pageHeaders(referer: string): Record<string, string> {
|
|
108
|
+
return {
|
|
109
|
+
'User-Agent': BROWSER_UA,
|
|
110
|
+
Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
|
|
111
|
+
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
|
|
112
|
+
'Cache-Control': 'no-cache',
|
|
113
|
+
DNT: '1',
|
|
114
|
+
Pragma: 'no-cache',
|
|
115
|
+
Referer: referer,
|
|
116
|
+
'Upgrade-Insecure-Requests': '1',
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function apiHeaders(referer: string, cookieHdr: string, xsrf: string): Record<string, string> {
|
|
121
|
+
const h: Record<string, string> = {
|
|
122
|
+
'User-Agent': BROWSER_UA,
|
|
123
|
+
Accept: 'application/json, text/plain, */*',
|
|
124
|
+
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
|
|
125
|
+
'Content-Type': 'application/json',
|
|
126
|
+
Origin: ORIGIN,
|
|
127
|
+
Referer: referer,
|
|
128
|
+
'Cache-Control': 'no-cache',
|
|
129
|
+
Pragma: 'no-cache',
|
|
130
|
+
DNT: '1',
|
|
131
|
+
Cookie: cookieHdr,
|
|
132
|
+
};
|
|
133
|
+
if (xsrf) h['X-XSRF-TOKEN'] = xsrf;
|
|
134
|
+
return h;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export async function generateEmail(domain?: string | null): Promise<InternalEmailInfo> {
|
|
138
|
+
const locale = localeFromDomain(domain);
|
|
139
|
+
const pageURL = `${ORIGIN}/public/${encodeURIComponent(locale)}`;
|
|
140
|
+
|
|
141
|
+
const res = await fetchWithTimeout(pageURL, { headers: pageHeaders(pageURL) });
|
|
142
|
+
if (!res.ok) {
|
|
143
|
+
throw new Error(`tempmailg page: ${res.status}`);
|
|
144
|
+
}
|
|
145
|
+
const html = await res.text();
|
|
146
|
+
const csrf = parseCsrf(html);
|
|
147
|
+
let cookieHdr = mergeCookies('', setCookieLines(res.headers));
|
|
148
|
+
let xsrf = xsrfFromCookies(cookieHdr);
|
|
149
|
+
if (!xsrf) {
|
|
150
|
+
throw new Error('tempmailg: missing XSRF-TOKEN cookie');
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const postURL = `${ORIGIN}/public/get_messages`;
|
|
154
|
+
const res2 = await fetchWithTimeout(postURL, {
|
|
155
|
+
method: 'POST',
|
|
156
|
+
headers: apiHeaders(pageURL, cookieHdr, xsrf),
|
|
157
|
+
body: JSON.stringify({ _token: csrf }),
|
|
158
|
+
});
|
|
159
|
+
if (!res2.ok) {
|
|
160
|
+
throw new Error(`tempmailg get_messages: ${res2.status}`);
|
|
161
|
+
}
|
|
162
|
+
const wrap = (await res2.json()) as { status?: boolean; mailbox?: string; messages?: unknown[] };
|
|
163
|
+
if (!wrap.status || !wrap.mailbox) {
|
|
164
|
+
throw new Error('tempmailg: create mailbox failed');
|
|
165
|
+
}
|
|
166
|
+
cookieHdr = mergeCookies(cookieHdr, setCookieLines(res2.headers));
|
|
167
|
+
const xsrf2 = xsrfFromCookies(cookieHdr);
|
|
168
|
+
if (xsrf2) xsrf = xsrf2;
|
|
169
|
+
|
|
170
|
+
const token = b64EncodeJson({ l: locale, c: cookieHdr, s: csrf });
|
|
171
|
+
return {
|
|
172
|
+
channel: CHANNEL,
|
|
173
|
+
email: wrap.mailbox,
|
|
174
|
+
token,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export async function getEmails(email: string, token: string): Promise<Email[]> {
|
|
179
|
+
const sess = decodeToken(token);
|
|
180
|
+
const locale = sess.l || 'zh';
|
|
181
|
+
const pageURL = `${ORIGIN}/public/${encodeURIComponent(locale)}`;
|
|
182
|
+
const postURL = `${ORIGIN}/public/get_messages`;
|
|
183
|
+
const xsrf = xsrfFromCookies(sess.c);
|
|
184
|
+
|
|
185
|
+
const res = await fetchWithTimeout(postURL, {
|
|
186
|
+
method: 'POST',
|
|
187
|
+
headers: apiHeaders(pageURL, sess.c, xsrf),
|
|
188
|
+
body: JSON.stringify({ _token: sess.s }),
|
|
189
|
+
});
|
|
190
|
+
if (!res.ok) {
|
|
191
|
+
throw new Error(`tempmailg get_messages: ${res.status}`);
|
|
192
|
+
}
|
|
193
|
+
const wrap = (await res.json()) as { status?: boolean; mailbox?: string; messages?: unknown[] };
|
|
194
|
+
if (!wrap.status) {
|
|
195
|
+
throw new Error('tempmailg: get_messages failed');
|
|
196
|
+
}
|
|
197
|
+
if (wrap.mailbox && wrap.mailbox.trim().toLowerCase() !== email.trim().toLowerCase()) {
|
|
198
|
+
throw new Error('tempmailg: mailbox mismatch');
|
|
199
|
+
}
|
|
200
|
+
const rawList = Array.isArray(wrap.messages) ? wrap.messages : [];
|
|
201
|
+
const out: Email[] = [];
|
|
202
|
+
for (const rm of rawList) {
|
|
203
|
+
if (rm && typeof rm === 'object') {
|
|
204
|
+
out.push(normalizeEmail(rm as Record<string, unknown>, email));
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return out;
|
|
208
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { InternalEmailInfo, Email, Channel } from '../types';
|
|
2
|
+
import { normalizeEmail } from '../normalize';
|
|
3
|
+
import { fetchWithTimeout } from '../retry';
|
|
4
|
+
|
|
5
|
+
const CHANNEL: Channel = 'tmpmails';
|
|
6
|
+
const ORIGIN = 'https://tmpmails.com';
|
|
7
|
+
const TOKEN_SEP = '\t';
|
|
8
|
+
|
|
9
|
+
const EMAIL_RE = /[a-zA-Z0-9._-]+@tmpmails\.com/g;
|
|
10
|
+
const PAGE_CHUNK_RE = /\/_next\/static\/chunks\/app\/%5Blocale%5D\/page-[a-f0-9]+\.js/;
|
|
11
|
+
const INBOX_ACTION_RE = /"([0-9a-f]+)",[^,]+,void 0,[^,]+,"getInboxList"/;
|
|
12
|
+
|
|
13
|
+
function localeFromDomain(domain?: string | null): string {
|
|
14
|
+
const s = String(domain ?? '').trim();
|
|
15
|
+
return s || 'zh';
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function setCookieLines(headers: Headers): string[] {
|
|
19
|
+
const h = headers as Headers & { getSetCookie?: () => string[] };
|
|
20
|
+
if (typeof h.getSetCookie === 'function') {
|
|
21
|
+
return h.getSetCookie();
|
|
22
|
+
}
|
|
23
|
+
const one = headers.get('set-cookie');
|
|
24
|
+
return one ? [one] : [];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function parseUserSign(headers: Headers): string {
|
|
28
|
+
for (const line of setCookieLines(headers)) {
|
|
29
|
+
const m = /^user_sign=([^;]+)/i.exec(line);
|
|
30
|
+
if (m) {
|
|
31
|
+
const v = m[1].trim();
|
|
32
|
+
if (v) return v;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
throw new Error('tmpmails: missing user_sign cookie');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function pickEmail(html: string): string {
|
|
39
|
+
const support = 'support@tmpmails.com';
|
|
40
|
+
const counts = new Map<string, number>();
|
|
41
|
+
for (const m of html.matchAll(EMAIL_RE)) {
|
|
42
|
+
const addr = m[0];
|
|
43
|
+
if (addr.toLowerCase() === support) continue;
|
|
44
|
+
counts.set(addr, (counts.get(addr) ?? 0) + 1);
|
|
45
|
+
}
|
|
46
|
+
if (counts.size === 0) {
|
|
47
|
+
throw new Error('tmpmails: no inbox address in page');
|
|
48
|
+
}
|
|
49
|
+
let best = '';
|
|
50
|
+
let bestC = 0;
|
|
51
|
+
for (const [addr, c] of counts) {
|
|
52
|
+
if (c > bestC || (c === bestC && addr < best)) {
|
|
53
|
+
best = addr;
|
|
54
|
+
bestC = c;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return best;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function fetchInboxActionID(html: string): Promise<string> {
|
|
61
|
+
const sub = html.match(PAGE_CHUNK_RE)?.[0];
|
|
62
|
+
if (!sub) throw new Error('tmpmails: page chunk script not found');
|
|
63
|
+
const res = await fetchWithTimeout(`${ORIGIN}${sub}`, {
|
|
64
|
+
headers: {
|
|
65
|
+
'User-Agent':
|
|
66
|
+
'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',
|
|
67
|
+
Accept: '*/*',
|
|
68
|
+
Referer: `${ORIGIN}/`,
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
if (!res.ok) {
|
|
72
|
+
throw new Error(`tmpmails page chunk: ${res.status}`);
|
|
73
|
+
}
|
|
74
|
+
const js = await res.text();
|
|
75
|
+
const m = js.match(INBOX_ACTION_RE);
|
|
76
|
+
if (!m?.[1]) throw new Error('tmpmails: getInboxList action not found in chunk');
|
|
77
|
+
return m[1];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export async function generateEmail(domain?: string | null): Promise<InternalEmailInfo> {
|
|
81
|
+
const loc = localeFromDomain(domain);
|
|
82
|
+
const pageUrl = `${ORIGIN}/${loc}`;
|
|
83
|
+
const res = await fetchWithTimeout(pageUrl, {
|
|
84
|
+
headers: {
|
|
85
|
+
'User-Agent':
|
|
86
|
+
'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',
|
|
87
|
+
Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
|
|
88
|
+
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',
|
|
89
|
+
'Cache-Control': 'no-cache',
|
|
90
|
+
DNT: '1',
|
|
91
|
+
Pragma: 'no-cache',
|
|
92
|
+
Referer: pageUrl,
|
|
93
|
+
'Upgrade-Insecure-Requests': '1',
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
if (!res.ok) {
|
|
97
|
+
throw new Error(`tmpmails generate: ${res.status}`);
|
|
98
|
+
}
|
|
99
|
+
const html = await res.text();
|
|
100
|
+
const userSign = parseUserSign(res.headers);
|
|
101
|
+
const email = pickEmail(html);
|
|
102
|
+
const actionId = await fetchInboxActionID(html);
|
|
103
|
+
const token = `${loc}${TOKEN_SEP}${userSign}${TOKEN_SEP}${actionId}`;
|
|
104
|
+
return { channel: CHANNEL, email, token };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function parseInboxResponse(recipient: string, raw: string): Email[] {
|
|
108
|
+
const text = raw.split('"$undefined"').join('null');
|
|
109
|
+
const out: Email[] = [];
|
|
110
|
+
let dataErr: Error | null = null;
|
|
111
|
+
for (const line of text.split('\n')) {
|
|
112
|
+
const trimmed = line.trim();
|
|
113
|
+
if (!trimmed) continue;
|
|
114
|
+
const colon = trimmed.indexOf(':');
|
|
115
|
+
if (colon <= 0) continue;
|
|
116
|
+
const jsonPart = trimmed.slice(colon + 1).trim();
|
|
117
|
+
if (!jsonPart.startsWith('{')) continue;
|
|
118
|
+
let wrap: { code?: number; data?: { list?: unknown[] } };
|
|
119
|
+
try {
|
|
120
|
+
wrap = JSON.parse(jsonPart) as { code?: number; data?: { list?: unknown[] } };
|
|
121
|
+
} catch {
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
if (wrap.code !== 200 || !wrap.data) continue;
|
|
125
|
+
const list = wrap.data.list;
|
|
126
|
+
if (!Array.isArray(list)) {
|
|
127
|
+
dataErr = new Error('tmpmails: invalid list');
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
for (const item of list) {
|
|
131
|
+
if (item && typeof item === 'object') {
|
|
132
|
+
out.push(normalizeEmail(item, recipient));
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return out;
|
|
136
|
+
}
|
|
137
|
+
if (dataErr) throw dataErr;
|
|
138
|
+
throw new Error('tmpmails: inbox payload not found');
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export async function getEmails(email: string, token: string): Promise<Email[]> {
|
|
142
|
+
const parts = token.split(TOKEN_SEP);
|
|
143
|
+
if (parts.length !== 3) {
|
|
144
|
+
throw new Error('tmpmails: invalid session token');
|
|
145
|
+
}
|
|
146
|
+
const [locale, userSign, actionId] = parts;
|
|
147
|
+
const postUrl = `${ORIGIN}/${locale}`;
|
|
148
|
+
const body = JSON.stringify([userSign, email, 0]);
|
|
149
|
+
const res = await fetchWithTimeout(postUrl, {
|
|
150
|
+
method: 'POST',
|
|
151
|
+
headers: {
|
|
152
|
+
'User-Agent':
|
|
153
|
+
'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',
|
|
154
|
+
Accept: 'text/x-component',
|
|
155
|
+
'Content-Type': 'text/plain;charset=UTF-8',
|
|
156
|
+
'Next-Action': actionId,
|
|
157
|
+
Origin: ORIGIN,
|
|
158
|
+
Referer: postUrl,
|
|
159
|
+
Cookie: `NEXT_LOCALE=${locale}; user_sign=${userSign}`,
|
|
160
|
+
},
|
|
161
|
+
body,
|
|
162
|
+
});
|
|
163
|
+
if (!res.ok) {
|
|
164
|
+
throw new Error(`tmpmails inbox: ${res.status}`);
|
|
165
|
+
}
|
|
166
|
+
const raw = await res.text();
|
|
167
|
+
return parseInboxResponse(email, raw);
|
|
168
|
+
}
|