tempmail_sdk 1.3.3 → 1.3.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.
- checksums.yaml +4 -4
- data/lib/tempmail_sdk/providers/altmails.rb +99 -0
- data/lib/tempmail_sdk/providers/apihz.rb +138 -0
- data/lib/tempmail_sdk/providers/awamail.rb +95 -0
- data/lib/tempmail_sdk/providers/best_temp_mail.rb +77 -0
- data/lib/tempmail_sdk/providers/chatgpt_org_uk.rb +129 -0
- data/lib/tempmail_sdk/providers/disposablemail.rb +125 -0
- data/lib/tempmail_sdk/providers/disposablemail_app.rb +66 -0
- data/lib/tempmail_sdk/providers/email10min.rb +152 -0
- data/lib/tempmail_sdk/providers/emailnator.rb +132 -0
- data/lib/tempmail_sdk/providers/emailtemp_org.rb +131 -0
- data/lib/tempmail_sdk/providers/expressinboxhub.rb +118 -0
- data/lib/tempmail_sdk/providers/fakemail.rb +143 -0
- data/lib/tempmail_sdk/providers/haribu.rb +29 -3
- data/lib/tempmail_sdk/providers/linshiyouxiang_net.rb +85 -0
- data/lib/tempmail_sdk/providers/mail_sunls.rb +42 -1
- data/lib/tempmail_sdk/providers/mail_td.rb +161 -0
- data/lib/tempmail_sdk/providers/mailcat_ai.rb +52 -0
- data/lib/tempmail_sdk/providers/maildrop.rb +79 -4
- data/lib/tempmail_sdk/providers/mailgolem.rb +96 -0
- data/lib/tempmail_sdk/providers/mailinator.rb +9 -2
- data/lib/tempmail_sdk/providers/mailtemp_cc.rb +91 -0
- data/lib/tempmail_sdk/providers/minuteinbox.rb +174 -0
- data/lib/tempmail_sdk/providers/moakt.rb +29 -3
- data/lib/tempmail_sdk/providers/mohmal.rb +208 -0
- data/lib/tempmail_sdk/providers/mytempmail_cc.rb +76 -0
- data/lib/tempmail_sdk/providers/openinbox.rb +102 -0
- data/lib/tempmail_sdk/providers/smail_pw.rb +156 -0
- data/lib/tempmail_sdk/providers/socketio_mail.rb +25 -0
- data/lib/tempmail_sdk/providers/tempgbox.rb +118 -0
- data/lib/tempmail_sdk/providers/tempmail_cn.rb +111 -0
- data/lib/tempmail_sdk/providers/tenminutemail_net.rb +183 -0
- data/lib/tempmail_sdk/providers/twentyfourmail_chacuo.rb +83 -0
- data/lib/tempmail_sdk/providers/vip_215.rb +171 -0
- data/lib/tempmail_sdk/registry.rb +163 -1
- data/lib/tempmail_sdk/version.rb +1 -1
- metadata +30 -2
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module TempmailSdk
|
|
6
|
+
module Providers
|
|
7
|
+
# disposablemail.app 渠道实现
|
|
8
|
+
#
|
|
9
|
+
# 纯 REST JSON API,无需认证。
|
|
10
|
+
# 创建收件箱: POST /api/inbox (body: {})
|
|
11
|
+
# 获取邮件: GET /api/inbox/emails?token={token}
|
|
12
|
+
module DisposablemailApp
|
|
13
|
+
CHANNEL = "disposablemail-app"
|
|
14
|
+
API_BASE = "https://disposablemail.app/api"
|
|
15
|
+
|
|
16
|
+
HEADERS = {
|
|
17
|
+
"Accept" => "application/json, text/plain, */*",
|
|
18
|
+
"Accept-Language" => "zh-CN,zh;q=0.9,en;q=0.8",
|
|
19
|
+
"Content-Type" => "application/json",
|
|
20
|
+
"Origin" => "https://disposablemail.app",
|
|
21
|
+
"Referer" => "https://disposablemail.app/",
|
|
22
|
+
"User-Agent" => "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " \
|
|
23
|
+
"(KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"
|
|
24
|
+
}.freeze
|
|
25
|
+
|
|
26
|
+
module_function
|
|
27
|
+
|
|
28
|
+
# 创建临时邮箱
|
|
29
|
+
# @return [EmailInfo]
|
|
30
|
+
def generate_email
|
|
31
|
+
resp = Http.post("#{API_BASE}/inbox", headers: HEADERS, body: "{}")
|
|
32
|
+
resp.raise_for_status
|
|
33
|
+
data = resp.json
|
|
34
|
+
address = data["address"].to_s.strip
|
|
35
|
+
token = data["token"].to_s.strip
|
|
36
|
+
raise "disposablemail-app: address 或 token 为空" if address.empty? || token.empty?
|
|
37
|
+
|
|
38
|
+
info = EmailInfo.new(channel: CHANNEL, email: address, token: token)
|
|
39
|
+
info.expires_at = data["expiresAt"].to_s
|
|
40
|
+
info.created_at = data["createdAt"].to_s
|
|
41
|
+
info
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# 获取邮件列表
|
|
45
|
+
# @param email [String]
|
|
46
|
+
# @param token [String] API 返回的 token
|
|
47
|
+
# @return [Array<Email>]
|
|
48
|
+
def get_emails(email, token)
|
|
49
|
+
raise "disposablemail-app: token 为空" if token.to_s.empty?
|
|
50
|
+
|
|
51
|
+
get_headers = HEADERS.reject { |k, _| k == "Content-Type" }
|
|
52
|
+
resp = Http.get("#{API_BASE}/inbox/emails?token=#{token}", headers: get_headers)
|
|
53
|
+
resp.raise_for_status
|
|
54
|
+
data = resp.json
|
|
55
|
+
emails_raw = data["emails"]
|
|
56
|
+
return [] unless emails_raw.is_a?(Array) && !emails_raw.empty?
|
|
57
|
+
|
|
58
|
+
emails_raw.filter_map do |raw|
|
|
59
|
+
next unless raw.is_a?(Hash)
|
|
60
|
+
|
|
61
|
+
Normalize.normalize_email(raw, email)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "base64"
|
|
4
|
+
require "json"
|
|
5
|
+
|
|
6
|
+
module TempmailSdk
|
|
7
|
+
module Providers
|
|
8
|
+
# email10min.com 渠道实现
|
|
9
|
+
#
|
|
10
|
+
# 流程:
|
|
11
|
+
# 1. GET /zh 获取 CSRF meta token + session cookies + HTML 中的邮箱地址
|
|
12
|
+
# 2. POST /messages?<ts> 携带 Cookie + _token 表单 获取邮件列表
|
|
13
|
+
# token 格式: "e10m:" + base64(JSON{c: cookie, t: csrf})
|
|
14
|
+
module Email10min
|
|
15
|
+
CHANNEL = "email10min"
|
|
16
|
+
BASE_URL = "https://email10min.com"
|
|
17
|
+
TOK_PREFIX = "e10m:"
|
|
18
|
+
|
|
19
|
+
CSRF_META_RE = /<meta\s+name="csrf-token"\s+content="([^"]+)"/.freeze
|
|
20
|
+
CSRF_INPUT_RE = /<input[^>]+name="_token"[^>]+value="([^"]+)"/.freeze
|
|
21
|
+
EMAIL_ID_RE = /id="emailAddress"[^>]*>([^<]+)/.freeze
|
|
22
|
+
EMAIL_DATA_RE = /data-email="([^"]+@[^"]+)"/.freeze
|
|
23
|
+
EMAIL_JSON_RE = /"mailbox"\s*:\s*"([^"]+@[^"]+)"/.freeze
|
|
24
|
+
EMAIL_GENERIC_RE = /([a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,})/.freeze
|
|
25
|
+
|
|
26
|
+
BROWSER_HEADERS = {
|
|
27
|
+
"Accept" => "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8",
|
|
28
|
+
"Accept-Language" => "zh-CN,zh;q=0.9,en;q=0.8",
|
|
29
|
+
"User-Agent" => "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " \
|
|
30
|
+
"(KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36 Edg/146.0.0.0"
|
|
31
|
+
}.freeze
|
|
32
|
+
|
|
33
|
+
AJAX_HEADERS = {
|
|
34
|
+
"Accept" => "application/json, text/plain, */*",
|
|
35
|
+
"Accept-Language" => "zh-CN,zh;q=0.9,en;q=0.8",
|
|
36
|
+
"Content-Type" => "application/x-www-form-urlencoded; charset=UTF-8",
|
|
37
|
+
"Origin" => BASE_URL,
|
|
38
|
+
"Referer" => "#{BASE_URL}/zh",
|
|
39
|
+
"User-Agent" => "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " \
|
|
40
|
+
"(KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36 Edg/146.0.0.0",
|
|
41
|
+
"X-Requested-With" => "XMLHttpRequest"
|
|
42
|
+
}.freeze
|
|
43
|
+
|
|
44
|
+
module_function
|
|
45
|
+
|
|
46
|
+
# 从响应的 Set-Cookie 头合并为 Cookie 字符串
|
|
47
|
+
def merge_cookies(resp)
|
|
48
|
+
parts = []
|
|
49
|
+
resp.set_cookies.each do |line|
|
|
50
|
+
first = line.split(";", 2).first.to_s.strip
|
|
51
|
+
parts << first unless first.empty?
|
|
52
|
+
end
|
|
53
|
+
parts.join("; ")
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# token 编码
|
|
57
|
+
def encode_token(cookie, csrf)
|
|
58
|
+
raw = JSON.generate("c" => cookie, "t" => csrf)
|
|
59
|
+
TOK_PREFIX + Base64.strict_encode64(raw)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# token 解码,返回 [cookie, csrf]
|
|
63
|
+
def decode_token(token)
|
|
64
|
+
raise "email10min: 无效 session token" unless token.to_s.start_with?(TOK_PREFIX)
|
|
65
|
+
|
|
66
|
+
raw = Base64.strict_decode64(token[TOK_PREFIX.length..])
|
|
67
|
+
data = JSON.parse(raw)
|
|
68
|
+
cookie = data["c"].to_s
|
|
69
|
+
csrf = data["t"].to_s
|
|
70
|
+
raise "email10min: 无效 session token(字段缺失)" if cookie.empty? || csrf.empty?
|
|
71
|
+
|
|
72
|
+
[cookie, csrf]
|
|
73
|
+
rescue ArgumentError, JSON::ParserError
|
|
74
|
+
raise "email10min: 无效 session token"
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# 从 HTML 中提取 CSRF token
|
|
78
|
+
def extract_csrf(html)
|
|
79
|
+
m = html.match(CSRF_META_RE) || html.match(CSRF_INPUT_RE)
|
|
80
|
+
raise "email10min: 未找到 CSRF token" unless m
|
|
81
|
+
|
|
82
|
+
m[1]
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# 从 HTML 中提取邮箱地址
|
|
86
|
+
def extract_email(html)
|
|
87
|
+
[EMAIL_ID_RE, EMAIL_DATA_RE, EMAIL_JSON_RE].each do |re|
|
|
88
|
+
if (m = html.match(re))
|
|
89
|
+
addr = m[1].strip
|
|
90
|
+
return addr if addr.include?("@")
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
if (m = html.match(EMAIL_GENERIC_RE))
|
|
94
|
+
addr = m[1].strip
|
|
95
|
+
return addr unless addr.include?("email10min") || addr.include?("example")
|
|
96
|
+
end
|
|
97
|
+
raise "email10min: 未找到邮箱地址"
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# 创建临时邮箱
|
|
101
|
+
# @return [EmailInfo]
|
|
102
|
+
def generate_email
|
|
103
|
+
resp = Http.get("#{BASE_URL}/zh", headers: BROWSER_HEADERS)
|
|
104
|
+
resp.raise_for_status
|
|
105
|
+
cookie = merge_cookies(resp)
|
|
106
|
+
html = resp.body
|
|
107
|
+
csrf = extract_csrf(html)
|
|
108
|
+
email = extract_email(html)
|
|
109
|
+
EmailInfo.new(channel: CHANNEL, email: email, token: encode_token(cookie, csrf))
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# 获取邮件列表
|
|
113
|
+
# @param email [String]
|
|
114
|
+
# @param token [String]
|
|
115
|
+
# @return [Array<Email>]
|
|
116
|
+
def get_emails(email, token)
|
|
117
|
+
cookie, csrf = decode_token(token)
|
|
118
|
+
ts = (Time.now.to_f * 1000).to_i
|
|
119
|
+
body = "_token=#{csrf}&captcha="
|
|
120
|
+
resp = Http.post("#{BASE_URL}/messages?#{ts}",
|
|
121
|
+
headers: AJAX_HEADERS.merge("Cookie" => cookie),
|
|
122
|
+
body: body)
|
|
123
|
+
resp.raise_for_status
|
|
124
|
+
data = JSON.parse(resp.body)
|
|
125
|
+
messages = data["messages"]
|
|
126
|
+
return [] unless messages.is_a?(Array) && !messages.empty?
|
|
127
|
+
|
|
128
|
+
messages.each_with_index.map do |msg, idx|
|
|
129
|
+
id = msg["id"] || msg["message_id"] || idx
|
|
130
|
+
from_addr = msg["from"].to_s
|
|
131
|
+
from_addr = msg["sender"].to_s if from_addr.empty?
|
|
132
|
+
text = msg["text"].to_s
|
|
133
|
+
text = msg["body"].to_s if text.empty?
|
|
134
|
+
html_body = msg["html"].to_s
|
|
135
|
+
html_body = msg["body_html"].to_s if html_body.empty?
|
|
136
|
+
date = msg["date"].to_s
|
|
137
|
+
date = msg["created_at"].to_s if date.empty?
|
|
138
|
+
Normalize.normalize_email({
|
|
139
|
+
"id" => id.to_s,
|
|
140
|
+
"from" => from_addr,
|
|
141
|
+
"to" => msg["to"].to_s.empty? ? email : msg["to"],
|
|
142
|
+
"subject" => msg["subject"].to_s,
|
|
143
|
+
"text" => text,
|
|
144
|
+
"html" => html_body,
|
|
145
|
+
"date" => date,
|
|
146
|
+
"isRead" => false
|
|
147
|
+
}, email)
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "uri"
|
|
5
|
+
|
|
6
|
+
module TempmailSdk
|
|
7
|
+
module Providers
|
|
8
|
+
# emailnator.com 渠道实现
|
|
9
|
+
#
|
|
10
|
+
# 流程:
|
|
11
|
+
# 1. GET / 获取 XSRF-TOKEN cookie(需 URL 解码)和 session cookie
|
|
12
|
+
# 2. POST /generate-email {"email":["plusGmail","dotGmail","googleMail"]} 创建邮箱
|
|
13
|
+
# 3. POST /message-list {"email":"..."} 获取邮件列表
|
|
14
|
+
# 4. POST /message-list {"email":"...","messageID":"..."} 获取单封 HTML
|
|
15
|
+
# token 存储 JSON {"xsrfToken":"...","cookie":"..."}
|
|
16
|
+
module Emailnator
|
|
17
|
+
CHANNEL = "emailnator"
|
|
18
|
+
BASE_URL = "https://www.emailnator.com"
|
|
19
|
+
OPTIONS = %w[plusGmail dotGmail googleMail].freeze
|
|
20
|
+
|
|
21
|
+
BROWSER_HEADERS = {
|
|
22
|
+
"Accept" => "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
|
|
23
|
+
"User-Agent" => "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " \
|
|
24
|
+
"(KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"
|
|
25
|
+
}.freeze
|
|
26
|
+
|
|
27
|
+
AJAX_HEADERS = {
|
|
28
|
+
"Accept" => "application/json, text/plain, */*",
|
|
29
|
+
"Content-Type" => "application/json",
|
|
30
|
+
"Origin" => BASE_URL,
|
|
31
|
+
"Referer" => "#{BASE_URL}/",
|
|
32
|
+
"User-Agent" => "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " \
|
|
33
|
+
"(KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36",
|
|
34
|
+
"X-Requested-With" => "XMLHttpRequest"
|
|
35
|
+
}.freeze
|
|
36
|
+
|
|
37
|
+
module_function
|
|
38
|
+
|
|
39
|
+
# 从响应的 Set-Cookie 头合并为 Cookie 字符串
|
|
40
|
+
def merge_cookies(resp)
|
|
41
|
+
parts = []
|
|
42
|
+
resp.set_cookies.each do |line|
|
|
43
|
+
first = line.split(";", 2).first.to_s.strip
|
|
44
|
+
parts << first unless first.empty?
|
|
45
|
+
end
|
|
46
|
+
parts.join("; ")
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# 初始化 session:GET / 建立 XSRF-TOKEN 与 session cookie
|
|
50
|
+
# @return [Hash] {"xsrfToken" => ..., "cookie" => ...}
|
|
51
|
+
def init_session
|
|
52
|
+
resp = Http.get(BASE_URL, headers: BROWSER_HEADERS)
|
|
53
|
+
resp.raise_for_status
|
|
54
|
+
cookie = merge_cookies(resp)
|
|
55
|
+
xsrf = resp.cookie_value("XSRF-TOKEN").to_s
|
|
56
|
+
xsrf = URI.decode_www_form_component(xsrf) unless xsrf.empty?
|
|
57
|
+
raise "emailnator: 缺少 session cookie 或 XSRF token" if cookie.empty? || xsrf.empty?
|
|
58
|
+
|
|
59
|
+
{ "xsrfToken" => xsrf, "cookie" => cookie }
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# 携带 session 发送 POST JSON 请求,返回原始 body
|
|
63
|
+
def session_post(session, path, body)
|
|
64
|
+
resp = Http.post("#{BASE_URL}#{path}",
|
|
65
|
+
headers: AJAX_HEADERS.merge(
|
|
66
|
+
"Cookie" => session["cookie"],
|
|
67
|
+
"X-XSRF-TOKEN" => session["xsrfToken"]
|
|
68
|
+
),
|
|
69
|
+
json: body)
|
|
70
|
+
resp.raise_for_status
|
|
71
|
+
resp.body
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# 创建临时邮箱
|
|
75
|
+
# @return [EmailInfo]
|
|
76
|
+
def generate_email
|
|
77
|
+
session = init_session
|
|
78
|
+
raw = session_post(session, "/generate-email", { "email" => OPTIONS })
|
|
79
|
+
data = JSON.parse(raw)
|
|
80
|
+
emails_arr = data["email"]
|
|
81
|
+
email = emails_arr.is_a?(Array) ? emails_arr.find { |e| !e.to_s.empty? } : nil
|
|
82
|
+
raise "emailnator: 创建邮箱返回为空" if email.nil? || email.empty?
|
|
83
|
+
|
|
84
|
+
EmailInfo.new(channel: CHANNEL, email: email, token: JSON.generate(session))
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# 获取邮件列表
|
|
88
|
+
# @param email [String]
|
|
89
|
+
# @param token [String] JSON {"xsrfToken":"...","cookie":"..."}
|
|
90
|
+
# @return [Array<Email>]
|
|
91
|
+
def get_emails(email, token)
|
|
92
|
+
session = JSON.parse(token)
|
|
93
|
+
raise "emailnator: 无效 session token" \
|
|
94
|
+
if session["cookie"].to_s.empty? || session["xsrfToken"].to_s.empty?
|
|
95
|
+
|
|
96
|
+
raw = session_post(session, "/message-list", { "email" => email })
|
|
97
|
+
data = JSON.parse(raw)
|
|
98
|
+
rows = data["messageData"]
|
|
99
|
+
return [] unless rows.is_a?(Array) && !rows.empty?
|
|
100
|
+
|
|
101
|
+
rows.filter_map do |row|
|
|
102
|
+
next unless row.is_a?(Hash)
|
|
103
|
+
|
|
104
|
+
mid = row["messageID"].to_s
|
|
105
|
+
next if mid.empty? || mid.start_with?("ADS")
|
|
106
|
+
|
|
107
|
+
html_body = fetch_detail(session, email, mid)
|
|
108
|
+
Normalize.normalize_email({
|
|
109
|
+
"id" => mid,
|
|
110
|
+
"from" => row["from"].to_s,
|
|
111
|
+
"to" => email,
|
|
112
|
+
"subject" => row["subject"].to_s,
|
|
113
|
+
"html" => html_body,
|
|
114
|
+
"date" => row["time"].to_s,
|
|
115
|
+
"isRead" => false
|
|
116
|
+
}, email)
|
|
117
|
+
end
|
|
118
|
+
rescue JSON::ParserError
|
|
119
|
+
raise "emailnator: 无效 session token"
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# 拉取单封邮件详情 HTML
|
|
123
|
+
def fetch_detail(session, email, message_id)
|
|
124
|
+
raw = session_post(session, "/message-list", { "email" => email, "messageID" => message_id })
|
|
125
|
+
# 详情响应可能是 JSON 编码的 HTML 字符串,也可能是原始 HTML
|
|
126
|
+
JSON.parse(raw)
|
|
127
|
+
rescue StandardError
|
|
128
|
+
raw.to_s
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "uri"
|
|
5
|
+
|
|
6
|
+
module TempmailSdk
|
|
7
|
+
module Providers
|
|
8
|
+
# emailtemp.org 渠道实现(与 tempmailten.com 共享 Laravel 模板)
|
|
9
|
+
#
|
|
10
|
+
# 流程:
|
|
11
|
+
# 1. GET /en 获取 session cookie + CSRF meta token
|
|
12
|
+
# 2. POST /messages(form: _token={csrf}&captcha=)获取邮箱地址和邮件列表
|
|
13
|
+
# 3. GET /view/{id} 获取单封邮件 HTML 正文
|
|
14
|
+
# token 存储 "cookie\ncsrf"
|
|
15
|
+
module EmailtempOrg
|
|
16
|
+
CHANNEL = "emailtemp-org"
|
|
17
|
+
BASE_URL = "https://emailtemp.org"
|
|
18
|
+
|
|
19
|
+
CSRF_RE = /<meta\s+name="csrf-token"\s+content="([^"]+)"/.freeze
|
|
20
|
+
|
|
21
|
+
BROWSER_HEADERS = {
|
|
22
|
+
"Accept" => "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8",
|
|
23
|
+
"Accept-Language" => "en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7",
|
|
24
|
+
"User-Agent" => "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " \
|
|
25
|
+
"(KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"
|
|
26
|
+
}.freeze
|
|
27
|
+
|
|
28
|
+
AJAX_HEADERS = {
|
|
29
|
+
"Accept" => "application/json, text/javascript, */*; q=0.01",
|
|
30
|
+
"Accept-Language" => "en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7",
|
|
31
|
+
"Content-Type" => "application/x-www-form-urlencoded",
|
|
32
|
+
"Referer" => "#{BASE_URL}/en",
|
|
33
|
+
"User-Agent" => "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " \
|
|
34
|
+
"(KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36",
|
|
35
|
+
"X-Requested-With" => "XMLHttpRequest"
|
|
36
|
+
}.freeze
|
|
37
|
+
|
|
38
|
+
module_function
|
|
39
|
+
|
|
40
|
+
# 合并 Set-Cookie 为 Cookie 字符串
|
|
41
|
+
def merge_cookies(resp)
|
|
42
|
+
parts = []
|
|
43
|
+
resp.set_cookies.each do |line|
|
|
44
|
+
first = line.split(";", 2).first.to_s.strip
|
|
45
|
+
parts << first unless first.empty?
|
|
46
|
+
end
|
|
47
|
+
parts.join("; ")
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# POST /messages 携带 CSRF 与 Cookie
|
|
51
|
+
def post_messages(cookie, csrf)
|
|
52
|
+
form = "_token=#{URI.encode_www_form_component(csrf)}&captcha="
|
|
53
|
+
headers = AJAX_HEADERS.merge("X-CSRF-TOKEN" => csrf)
|
|
54
|
+
headers = headers.merge("Cookie" => cookie) unless cookie.to_s.empty?
|
|
55
|
+
Http.post("#{BASE_URL}/messages", headers: headers, body: form)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# 创建临时邮箱
|
|
59
|
+
# @return [EmailInfo]
|
|
60
|
+
def generate_email
|
|
61
|
+
r1 = Http.get("#{BASE_URL}/en", headers: BROWSER_HEADERS)
|
|
62
|
+
r1.raise_for_status
|
|
63
|
+
cookie = merge_cookies(r1)
|
|
64
|
+
m = r1.body.match(CSRF_RE)
|
|
65
|
+
raise "emailtemp-org: 未能从首页提取 CSRF token" unless m
|
|
66
|
+
|
|
67
|
+
csrf = m[1]
|
|
68
|
+
|
|
69
|
+
r2 = post_messages(cookie, csrf)
|
|
70
|
+
r2.raise_for_status
|
|
71
|
+
new_cookie = merge_cookies(r2)
|
|
72
|
+
cookie = new_cookie unless new_cookie.empty?
|
|
73
|
+
|
|
74
|
+
data = JSON.parse(r2.body)
|
|
75
|
+
email = data["mailbox"].to_s.strip
|
|
76
|
+
raise "emailtemp-org: 邮箱地址无效: #{email.inspect}" if email.empty? || !email.include?("@")
|
|
77
|
+
|
|
78
|
+
token = "#{cookie}\n#{csrf}"
|
|
79
|
+
EmailInfo.new(channel: CHANNEL, email: email, token: token)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# 获取邮件列表
|
|
83
|
+
# @param email [String]
|
|
84
|
+
# @param token [String] "cookie\ncsrf"
|
|
85
|
+
# @return [Array<Email>]
|
|
86
|
+
def get_emails(email, token)
|
|
87
|
+
raise "emailtemp-org: 邮箱地址为空" if email.to_s.strip.empty?
|
|
88
|
+
|
|
89
|
+
cookie, csrf = token.to_s.split("\n", 2)
|
|
90
|
+
csrf = cookie if csrf.nil? # 兼容旧 token 仅存 CSRF 的情形
|
|
91
|
+
cookie = "" if csrf.nil? || csrf == cookie
|
|
92
|
+
raise "emailtemp-org: 缺少 CSRF token" if csrf.to_s.empty?
|
|
93
|
+
|
|
94
|
+
resp = post_messages(cookie, csrf)
|
|
95
|
+
resp.raise_for_status
|
|
96
|
+
data = JSON.parse(resp.body)
|
|
97
|
+
messages = data["messages"]
|
|
98
|
+
return [] unless messages.is_a?(Array) && !messages.empty?
|
|
99
|
+
|
|
100
|
+
messages.map do |msg|
|
|
101
|
+
id = msg["id"].to_s
|
|
102
|
+
from_addr = msg["from_email"].to_s
|
|
103
|
+
if !msg["from"].to_s.empty? && msg["from"] != from_addr
|
|
104
|
+
from_addr = "#{msg['from']} <#{msg['from_email']}>"
|
|
105
|
+
end
|
|
106
|
+
html_body = fetch_view(cookie, id)
|
|
107
|
+
Normalize.normalize_email({
|
|
108
|
+
"id" => id,
|
|
109
|
+
"from" => from_addr,
|
|
110
|
+
"to" => email,
|
|
111
|
+
"subject" => msg["subject"].to_s,
|
|
112
|
+
"html" => html_body,
|
|
113
|
+
"isRead" => msg["is_seen"] == true
|
|
114
|
+
}, email)
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# 获取单封邮件 HTML 正文
|
|
119
|
+
def fetch_view(cookie, id)
|
|
120
|
+
return "" if id.to_s.empty?
|
|
121
|
+
|
|
122
|
+
headers = BROWSER_HEADERS.merge("Referer" => "#{BASE_URL}/en")
|
|
123
|
+
headers = headers.merge("Cookie" => cookie) unless cookie.to_s.empty?
|
|
124
|
+
resp = Http.get("#{BASE_URL}/view/#{URI.encode_www_form_component(id)}", headers: headers)
|
|
125
|
+
resp.ok? ? resp.body : ""
|
|
126
|
+
rescue StandardError
|
|
127
|
+
""
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "uri"
|
|
5
|
+
|
|
6
|
+
module TempmailSdk
|
|
7
|
+
module Providers
|
|
8
|
+
# expressinboxhub.com 渠道实现
|
|
9
|
+
#
|
|
10
|
+
# 流程:
|
|
11
|
+
# 1. GET / 获取 CSRF meta token + session cookies
|
|
12
|
+
# 2. POST /messages(form: _token={csrf})创建邮箱并返回消息列表
|
|
13
|
+
# token 格式:"cookie\ncsrf"
|
|
14
|
+
module Expressinboxhub
|
|
15
|
+
CHANNEL = "expressinboxhub"
|
|
16
|
+
BASE_URL = "https://expressinboxhub.com"
|
|
17
|
+
|
|
18
|
+
CSRF_RE = /<meta\s+name="csrf-token"\s+content="([^"]+)"/.freeze
|
|
19
|
+
|
|
20
|
+
BROWSER_HEADERS = {
|
|
21
|
+
"Accept" => "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8",
|
|
22
|
+
"Accept-Language" => "zh-CN,zh;q=0.9,en;q=0.8",
|
|
23
|
+
"User-Agent" => "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " \
|
|
24
|
+
"(KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"
|
|
25
|
+
}.freeze
|
|
26
|
+
|
|
27
|
+
AJAX_HEADERS = {
|
|
28
|
+
"Accept" => "application/json, text/plain, */*",
|
|
29
|
+
"Accept-Language" => "zh-CN,zh;q=0.9,en;q=0.8",
|
|
30
|
+
"Content-Type" => "application/x-www-form-urlencoded; charset=UTF-8",
|
|
31
|
+
"Origin" => BASE_URL,
|
|
32
|
+
"Referer" => "#{BASE_URL}/",
|
|
33
|
+
"User-Agent" => "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " \
|
|
34
|
+
"(KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36",
|
|
35
|
+
"X-Requested-With" => "XMLHttpRequest"
|
|
36
|
+
}.freeze
|
|
37
|
+
|
|
38
|
+
module_function
|
|
39
|
+
|
|
40
|
+
# 合并 Set-Cookie 为 Cookie 字符串
|
|
41
|
+
def merge_cookies(existing, resp)
|
|
42
|
+
parts = existing.to_s.empty? ? [] : [existing]
|
|
43
|
+
resp.set_cookies.each do |line|
|
|
44
|
+
first = line.split(";", 2).first.to_s.strip
|
|
45
|
+
parts << first unless first.empty?
|
|
46
|
+
end
|
|
47
|
+
parts.join("; ")
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# POST /messages 携带 CSRF 与 Cookie
|
|
51
|
+
def post_messages(cookie, csrf)
|
|
52
|
+
headers = AJAX_HEADERS.merge("X-CSRF-TOKEN" => csrf)
|
|
53
|
+
headers = headers.merge("Cookie" => cookie) unless cookie.to_s.empty?
|
|
54
|
+
Http.post("#{BASE_URL}/messages", headers: headers, body: "_token=#{URI.encode_www_form_component(csrf)}")
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# 创建临时邮箱
|
|
58
|
+
# @return [EmailInfo]
|
|
59
|
+
def generate_email
|
|
60
|
+
r1 = Http.get(BASE_URL, headers: BROWSER_HEADERS)
|
|
61
|
+
r1.raise_for_status
|
|
62
|
+
cookie = merge_cookies("", r1)
|
|
63
|
+
m = r1.body.match(CSRF_RE)
|
|
64
|
+
raise "expressinboxhub: 未找到 CSRF token" unless m
|
|
65
|
+
|
|
66
|
+
csrf = m[1]
|
|
67
|
+
|
|
68
|
+
r2 = post_messages(cookie, csrf)
|
|
69
|
+
r2.raise_for_status
|
|
70
|
+
cookie = merge_cookies(cookie, r2)
|
|
71
|
+
data = JSON.parse(r2.body)
|
|
72
|
+
email = data["mailbox"].to_s.strip
|
|
73
|
+
raise "expressinboxhub: 响应中未包含邮箱地址" if email.empty?
|
|
74
|
+
|
|
75
|
+
EmailInfo.new(channel: CHANNEL, email: email, token: "#{cookie}\n#{csrf}")
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# 获取邮件列表
|
|
79
|
+
# @param email [String]
|
|
80
|
+
# @param token [String] "cookie\ncsrf"
|
|
81
|
+
# @return [Array<Email>]
|
|
82
|
+
def get_emails(email, token)
|
|
83
|
+
cookie, csrf = token.to_s.split("\n", 2)
|
|
84
|
+
raise "expressinboxhub: 无效的 session token" if cookie.to_s.empty? || csrf.to_s.empty?
|
|
85
|
+
|
|
86
|
+
resp = post_messages(cookie, csrf)
|
|
87
|
+
resp.raise_for_status
|
|
88
|
+
data = JSON.parse(resp.body)
|
|
89
|
+
messages = data["messages"]
|
|
90
|
+
return [] unless messages.is_a?(Array) && !messages.empty?
|
|
91
|
+
|
|
92
|
+
messages.each_with_index.map do |msg, idx|
|
|
93
|
+
id = msg["id"].to_s
|
|
94
|
+
id = idx.to_s if id.empty?
|
|
95
|
+
from_addr = msg["from"].to_s
|
|
96
|
+
from_addr = msg["sender"].to_s if from_addr.empty?
|
|
97
|
+
to_addr = msg["to"].to_s
|
|
98
|
+
to_addr = email if to_addr.empty?
|
|
99
|
+
date = msg["receivedAt"].to_s
|
|
100
|
+
date = msg["date"].to_s if date.empty?
|
|
101
|
+
date = msg["created_at"].to_s if date.empty?
|
|
102
|
+
text = msg["text"].to_s
|
|
103
|
+
text = msg["body"].to_s if text.empty?
|
|
104
|
+
Normalize.normalize_email({
|
|
105
|
+
"id" => id,
|
|
106
|
+
"from" => from_addr,
|
|
107
|
+
"to" => to_addr,
|
|
108
|
+
"subject" => msg["subject"].to_s,
|
|
109
|
+
"text" => text,
|
|
110
|
+
"html" => msg["content"].to_s,
|
|
111
|
+
"date" => date,
|
|
112
|
+
"isRead" => false
|
|
113
|
+
}, email)
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|