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,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module TempmailSdk
|
|
6
|
+
module Providers
|
|
7
|
+
# mytempmail.cc 渠道实现
|
|
8
|
+
#
|
|
9
|
+
# 流程:POST /api/address 创建邮箱(JSON body),返回 token + address
|
|
10
|
+
# GET /api/mails/{token} 获取邮件列表
|
|
11
|
+
# 域名固定 nilvaro.com,有效期 600 秒
|
|
12
|
+
module MytempmailCc
|
|
13
|
+
CHANNEL = "mytempmail-cc"
|
|
14
|
+
BASE_URL = "https://api.mytempmail.cc"
|
|
15
|
+
|
|
16
|
+
HEADERS = {
|
|
17
|
+
"Content-Type" => "application/json",
|
|
18
|
+
"Accept" => "application/json"
|
|
19
|
+
}.freeze
|
|
20
|
+
|
|
21
|
+
CHARS = ("a".."z").to_a.freeze
|
|
22
|
+
|
|
23
|
+
module_function
|
|
24
|
+
|
|
25
|
+
# 生成随机 8 位小写字母用户名
|
|
26
|
+
# @return [String]
|
|
27
|
+
def random_name
|
|
28
|
+
Array.new(8) { CHARS.sample }.join
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# 创建 mytempmail.cc 临时邮箱
|
|
32
|
+
# @return [EmailInfo]
|
|
33
|
+
def generate_email
|
|
34
|
+
body = JSON.generate("domain" => "nilvaro.com", "name" => random_name, "expiry" => 600)
|
|
35
|
+
resp = Http.post(BASE_URL + "/api/address", headers: HEADERS, body: body, timeout: 15)
|
|
36
|
+
resp.raise_for_status
|
|
37
|
+
data = resp.json
|
|
38
|
+
email = data["address"].to_s.strip
|
|
39
|
+
token = data["token"].to_s.strip
|
|
40
|
+
raise "mytempmail-cc: 无效的邮箱响应" if email.empty? || !email.include?("@")
|
|
41
|
+
raise "mytempmail-cc: 响应中缺少 token" if token.empty?
|
|
42
|
+
|
|
43
|
+
EmailInfo.new(channel: CHANNEL, email: email, token: token)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# 获取 mytempmail.cc 邮件列表
|
|
47
|
+
# @param token [String]
|
|
48
|
+
# @param email [String]
|
|
49
|
+
# @return [Array<Email>]
|
|
50
|
+
def get_emails(token, email)
|
|
51
|
+
token = token.to_s.strip
|
|
52
|
+
raise "mytempmail-cc: token 为空" if token.empty?
|
|
53
|
+
|
|
54
|
+
resp = Http.get("#{BASE_URL}/api/mails/#{token}", headers: HEADERS, timeout: 15)
|
|
55
|
+
resp.raise_for_status
|
|
56
|
+
data = resp.json
|
|
57
|
+
results = data.is_a?(Hash) ? Array(data["results"]) : []
|
|
58
|
+
|
|
59
|
+
results.filter_map do |msg|
|
|
60
|
+
next unless msg.is_a?(Hash)
|
|
61
|
+
|
|
62
|
+
flat = {
|
|
63
|
+
"id" => msg["id"],
|
|
64
|
+
"from" => msg["from"],
|
|
65
|
+
"to" => email,
|
|
66
|
+
"subject" => msg["subject"],
|
|
67
|
+
"text" => msg["body_text"],
|
|
68
|
+
"html" => msg["body_html"],
|
|
69
|
+
"received_at" => msg["received_at"]
|
|
70
|
+
}
|
|
71
|
+
Normalize.normalize_email(flat, email)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "uri"
|
|
4
|
+
|
|
5
|
+
module TempmailSdk
|
|
6
|
+
module Providers
|
|
7
|
+
# openinbox.io 渠道实现
|
|
8
|
+
#
|
|
9
|
+
# 流程:POST /api/inbox 创建邮箱,返回 id + email
|
|
10
|
+
# GET /api/emails/inbox/{id}?page=1&limit=50 获取邮件列表
|
|
11
|
+
# GET /api/emails/{messageId} 获取邮件详情
|
|
12
|
+
# token 存储 inbox id
|
|
13
|
+
module Openinbox
|
|
14
|
+
CHANNEL = "openinbox"
|
|
15
|
+
BASE_URL = "https://api.openinbox.io/api"
|
|
16
|
+
|
|
17
|
+
HEADERS = {
|
|
18
|
+
"Accept" => "application/json, text/plain, */*",
|
|
19
|
+
"Origin" => "https://openinbox.io",
|
|
20
|
+
"Referer" => "https://openinbox.io/",
|
|
21
|
+
"User-Agent" => "Mozilla/5.0"
|
|
22
|
+
}.freeze
|
|
23
|
+
|
|
24
|
+
module_function
|
|
25
|
+
|
|
26
|
+
# 发起 JSON 请求
|
|
27
|
+
# @param method [String] "GET" 或 "POST"
|
|
28
|
+
# @param url [String]
|
|
29
|
+
# @return [Object] 解析后的 JSON
|
|
30
|
+
def json_request(method, url)
|
|
31
|
+
hdrs = HEADERS.dup
|
|
32
|
+
hdrs["Content-Type"] = "application/json" if method == "POST"
|
|
33
|
+
resp = method == "POST" ? Http.post(url, headers: hdrs, timeout: 15) : Http.get(url, headers: hdrs, timeout: 15)
|
|
34
|
+
resp.raise_for_status
|
|
35
|
+
resp.json
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# 将邮件 raw map 展平为标准字段
|
|
39
|
+
# @param raw [Hash]
|
|
40
|
+
# @param recipient [String]
|
|
41
|
+
# @return [Hash]
|
|
42
|
+
def flatten(raw, recipient)
|
|
43
|
+
{
|
|
44
|
+
"id" => raw["id"],
|
|
45
|
+
"from" => raw["from"],
|
|
46
|
+
"to" => recipient,
|
|
47
|
+
"subject" => raw["subject"],
|
|
48
|
+
"text" => raw["textBody"],
|
|
49
|
+
"html" => raw["htmlBody"],
|
|
50
|
+
"receivedAt" => raw["receivedAt"],
|
|
51
|
+
"isRead" => raw["isRead"],
|
|
52
|
+
"attachments" => []
|
|
53
|
+
}
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# 创建 openinbox.io 临时邮箱
|
|
57
|
+
# @return [EmailInfo]
|
|
58
|
+
def generate_email
|
|
59
|
+
data = json_request("POST", BASE_URL + "/inbox")
|
|
60
|
+
id = data["id"].to_s.strip
|
|
61
|
+
email = data["email"].to_s.strip
|
|
62
|
+
raise "openinbox: 无效的邮箱响应" if id.empty? || email.empty? || !email.include?("@")
|
|
63
|
+
|
|
64
|
+
EmailInfo.new(channel: CHANNEL, email: email, token: id)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# 获取 openinbox.io 邮件列表
|
|
68
|
+
# @param inbox_id [String] inbox id(token)
|
|
69
|
+
# @param email [String]
|
|
70
|
+
# @return [Array<Email>]
|
|
71
|
+
def get_emails(inbox_id, email)
|
|
72
|
+
inbox_id = inbox_id.to_s.strip
|
|
73
|
+
raise "openinbox: empty inbox id" if inbox_id.empty?
|
|
74
|
+
|
|
75
|
+
url = "#{BASE_URL}/emails/inbox/#{URI.encode_uri_component(inbox_id)}?page=1&limit=50"
|
|
76
|
+
data = json_request("GET", url)
|
|
77
|
+
rows = data.is_a?(Hash) ? Array(data["emails"]) : []
|
|
78
|
+
|
|
79
|
+
rows.filter_map do |row|
|
|
80
|
+
next unless row.is_a?(Hash)
|
|
81
|
+
|
|
82
|
+
raw = row
|
|
83
|
+
msg_id = row["id"].to_s.strip
|
|
84
|
+
unless msg_id.empty?
|
|
85
|
+
detail = fetch_detail(msg_id)
|
|
86
|
+
raw = detail if detail.is_a?(Hash)
|
|
87
|
+
end
|
|
88
|
+
Normalize.normalize_email(flatten(raw, email), email)
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# 获取单封邮件详情
|
|
93
|
+
# @param message_id [String]
|
|
94
|
+
# @return [Hash, nil]
|
|
95
|
+
def fetch_detail(message_id)
|
|
96
|
+
json_request("GET", "#{BASE_URL}/emails/#{URI.encode_uri_component(message_id)}")
|
|
97
|
+
rescue StandardError
|
|
98
|
+
nil
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "uri"
|
|
5
|
+
|
|
6
|
+
module TempmailSdk
|
|
7
|
+
module Providers
|
|
8
|
+
# smail.pw 渠道实现
|
|
9
|
+
#
|
|
10
|
+
# 流程:POST /_root.data body: intent=generate 创建邮箱,保存 __session cookie
|
|
11
|
+
# GET /_root.data + Cookie 获取邮件列表(解析 React Flight 格式或正则提取)
|
|
12
|
+
# GET /api/email/{id} 获取单封邮件正文
|
|
13
|
+
# token 存储 __session=xxx cookie 字符串
|
|
14
|
+
module SmailPw
|
|
15
|
+
CHANNEL = "smail-pw"
|
|
16
|
+
ROOT_DATA_URL = "https://smail.pw/_root.data"
|
|
17
|
+
|
|
18
|
+
HEADERS = {
|
|
19
|
+
"Accept" => "*/*",
|
|
20
|
+
"accept-language" => "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
|
|
21
|
+
"cache-control" => "no-cache",
|
|
22
|
+
"dnt" => "1",
|
|
23
|
+
"origin" => "https://smail.pw",
|
|
24
|
+
"pragma" => "no-cache",
|
|
25
|
+
"referer" => "https://smail.pw/",
|
|
26
|
+
"sec-fetch-dest" => "empty",
|
|
27
|
+
"sec-fetch-mode" => "cors",
|
|
28
|
+
"sec-fetch-site" => "same-origin",
|
|
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
|
+
QUOTED_INBOX_RE = /"([a-z0-9][a-z0-9.\-]*@smail\.pw)"/i
|
|
34
|
+
PLAIN_INBOX_RE = /\b([a-z0-9][a-z0-9.\-]*@smail\.pw)\b/i
|
|
35
|
+
MAIL_BLOCK_RE = /"id","([^"]+)","to_address","([^"]*)","from_name","([^"]*)","from_address","([^"]*)","subject","([^"]*)","time",(\d+)/
|
|
36
|
+
MAIL_BLOCK2_RE = /"id","([^"]+)","from_name","([^"]*)","from_address","([^"]*)","subject","([^"]*)","time",(\d+)/
|
|
37
|
+
|
|
38
|
+
module_function
|
|
39
|
+
|
|
40
|
+
# 从响应 Set-Cookie 中提取 __session cookie
|
|
41
|
+
# @param set_cookie_lines [Array<String>]
|
|
42
|
+
# @return [String]
|
|
43
|
+
def extract_session(set_cookie_lines)
|
|
44
|
+
set_cookie_lines.each do |line|
|
|
45
|
+
pair = line.split(";", 2).first.to_s.strip
|
|
46
|
+
return pair if pair.start_with?("__session=")
|
|
47
|
+
end
|
|
48
|
+
""
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# 从响应体中提取邮箱地址
|
|
52
|
+
# @param body [String]
|
|
53
|
+
# @return [String]
|
|
54
|
+
def extract_inbox(body)
|
|
55
|
+
m = body.match(QUOTED_INBOX_RE)
|
|
56
|
+
return m[1] if m
|
|
57
|
+
|
|
58
|
+
m = body.match(PLAIN_INBOX_RE)
|
|
59
|
+
m ? m[1] : ""
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# 从响应体解析邮件列表
|
|
63
|
+
# @param body [String]
|
|
64
|
+
# @param recipient [String]
|
|
65
|
+
# @return [Array<Hash>]
|
|
66
|
+
def parse_mails(body, recipient)
|
|
67
|
+
out = []
|
|
68
|
+
body.scan(MAIL_BLOCK_RE) do |id, to_addr, from_name, from_addr, subj, ts|
|
|
69
|
+
out << {
|
|
70
|
+
"id" => id, "to_address" => to_addr.empty? ? recipient : to_addr,
|
|
71
|
+
"from_name" => from_name, "from_address" => from_addr,
|
|
72
|
+
"subject" => subj, "date" => ts.to_f,
|
|
73
|
+
"text" => "", "html" => "", "attachments" => []
|
|
74
|
+
}
|
|
75
|
+
end
|
|
76
|
+
return out unless out.empty?
|
|
77
|
+
|
|
78
|
+
body.scan(MAIL_BLOCK2_RE) do |id, from_name, from_addr, subj, ts|
|
|
79
|
+
out << {
|
|
80
|
+
"id" => id, "to_address" => recipient,
|
|
81
|
+
"from_name" => from_name, "from_address" => from_addr,
|
|
82
|
+
"subject" => subj, "date" => ts.to_f,
|
|
83
|
+
"text" => "", "html" => "", "attachments" => []
|
|
84
|
+
}
|
|
85
|
+
end
|
|
86
|
+
out
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# 创建 smail.pw 临时邮箱
|
|
90
|
+
# @return [EmailInfo]
|
|
91
|
+
def generate_email
|
|
92
|
+
resp = Http.post(
|
|
93
|
+
ROOT_DATA_URL,
|
|
94
|
+
headers: HEADERS.merge("Content-Type" => "application/x-www-form-urlencoded;charset=UTF-8"),
|
|
95
|
+
body: "intent=generate",
|
|
96
|
+
timeout: 15
|
|
97
|
+
)
|
|
98
|
+
raise "smail.pw generate failed: #{resp.status_code}" unless resp.ok?
|
|
99
|
+
|
|
100
|
+
cookie = extract_session(resp.set_cookies)
|
|
101
|
+
raise "smail.pw: 未能提取 __session cookie" if cookie.empty?
|
|
102
|
+
|
|
103
|
+
email = extract_inbox(resp.body)
|
|
104
|
+
raise "smail.pw: 未能解析邮箱地址" if email.empty?
|
|
105
|
+
|
|
106
|
+
EmailInfo.new(channel: CHANNEL, email: email, token: cookie)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# 获取 smail.pw 邮件列表
|
|
110
|
+
# @param token [String] __session=xxx cookie 字符串
|
|
111
|
+
# @param email [String]
|
|
112
|
+
# @return [Array<Email>]
|
|
113
|
+
def get_emails(token, email)
|
|
114
|
+
resp = Http.get(
|
|
115
|
+
ROOT_DATA_URL,
|
|
116
|
+
headers: HEADERS.merge("Cookie" => token),
|
|
117
|
+
timeout: 15
|
|
118
|
+
)
|
|
119
|
+
raise "smail.pw get emails failed: #{resp.status_code}" unless resp.ok?
|
|
120
|
+
|
|
121
|
+
mails = parse_mails(resp.body, email)
|
|
122
|
+
return [] if mails.empty?
|
|
123
|
+
|
|
124
|
+
mails.map do |m|
|
|
125
|
+
ne = Normalize.normalize_email(m, email)
|
|
126
|
+
id = m["id"].to_s
|
|
127
|
+
unless id.empty?
|
|
128
|
+
body_html = fetch_body(token, id)
|
|
129
|
+
ne.html = body_html unless body_html.empty?
|
|
130
|
+
end
|
|
131
|
+
ne
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# 获取单封邮件正文
|
|
136
|
+
# @param token [String]
|
|
137
|
+
# @param id [String]
|
|
138
|
+
# @return [String]
|
|
139
|
+
def fetch_body(token, id)
|
|
140
|
+
return "" if id.empty?
|
|
141
|
+
|
|
142
|
+
resp = Http.get(
|
|
143
|
+
"https://smail.pw/api/email/#{URI.encode_uri_component(id)}",
|
|
144
|
+
headers: HEADERS.merge("Cookie" => token, "Accept" => "application/json"),
|
|
145
|
+
timeout: 15
|
|
146
|
+
)
|
|
147
|
+
return "" unless resp.ok?
|
|
148
|
+
|
|
149
|
+
data = resp.json
|
|
150
|
+
data.is_a?(Hash) ? data["body"].to_s : ""
|
|
151
|
+
rescue StandardError
|
|
152
|
+
""
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module TempmailSdk
|
|
4
|
+
module Providers
|
|
5
|
+
# socketio-mail 渠道桩实现
|
|
6
|
+
#
|
|
7
|
+
# 该渠道依赖 WebSocket/Socket.IO 协议,Ruby 标准 HTTP 客户端无法实现。
|
|
8
|
+
# 当前注册为桩,调用时抛出 NotImplementedProviderError。
|
|
9
|
+
module SocketioMail
|
|
10
|
+
CHANNEL = "socketio-mail"
|
|
11
|
+
|
|
12
|
+
module_function
|
|
13
|
+
|
|
14
|
+
# @raise [NotImplementedError]
|
|
15
|
+
def generate_email
|
|
16
|
+
raise NotImplementedProviderError, "channel not implemented yet: #{CHANNEL}"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# @raise [NotImplementedError]
|
|
20
|
+
def get_emails(_email, _token)
|
|
21
|
+
raise NotImplementedProviderError, "channel not implemented yet: #{CHANNEL}"
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module TempmailSdk
|
|
6
|
+
module Providers
|
|
7
|
+
# tempgbox.net 渠道实现
|
|
8
|
+
#
|
|
9
|
+
# 流程:POST /api/proxy?route=generate 创建邮箱(JSON body,X-Device-ID 头)
|
|
10
|
+
# POST /api/proxy?route=inbox 获取邮件列表
|
|
11
|
+
# 响应体为 HTML 页面,实际数据编码在 data-x 属性(base64 JSON)
|
|
12
|
+
# token 存储 device_id
|
|
13
|
+
module Tempgbox
|
|
14
|
+
CHANNEL = "tempgbox"
|
|
15
|
+
API_URL = "https://tempgbox.net/api/proxy"
|
|
16
|
+
|
|
17
|
+
module_function
|
|
18
|
+
|
|
19
|
+
# 生成随机 64 位十六进制 device id
|
|
20
|
+
# @return [String]
|
|
21
|
+
def random_device_id
|
|
22
|
+
require "securerandom"
|
|
23
|
+
SecureRandom.hex(32)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# 生成随机 IP 地址
|
|
27
|
+
# @return [String]
|
|
28
|
+
def random_ip
|
|
29
|
+
"#{rand(1..254)}.#{rand(1..254)}.#{rand(1..254)}.#{rand(1..254)}"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# 构造请求头
|
|
33
|
+
# @param device_id [String]
|
|
34
|
+
# @return [Hash]
|
|
35
|
+
def build_headers(device_id)
|
|
36
|
+
ip = random_ip
|
|
37
|
+
{
|
|
38
|
+
"Accept" => "text/html,application/json",
|
|
39
|
+
"Content-Type" => "application/json",
|
|
40
|
+
"Origin" => "https://tempgbox.net",
|
|
41
|
+
"Referer" => "https://tempgbox.net/",
|
|
42
|
+
"User-Agent" => "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " \
|
|
43
|
+
"(KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36",
|
|
44
|
+
"X-Device-ID" => device_id,
|
|
45
|
+
"X-Forwarded-For" => ip,
|
|
46
|
+
"X-Real-IP" => ip,
|
|
47
|
+
"X-Originating-IP" => ip
|
|
48
|
+
}
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# 从响应 HTML 中解码 data-x 属性(base64 JSON)
|
|
52
|
+
# @param body [String]
|
|
53
|
+
# @return [Hash]
|
|
54
|
+
def decode_payload(body)
|
|
55
|
+
require "base64"
|
|
56
|
+
m = body.match(/data-x=["']([^"']+)["']/)
|
|
57
|
+
raise "tempgbox: 响应中缺少 data-x 属性" unless m
|
|
58
|
+
|
|
59
|
+
raw = Base64.strict_decode64(m[1])
|
|
60
|
+
JSON.parse(raw)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# 发起 POST 请求并解码响应
|
|
64
|
+
# @param route [String]
|
|
65
|
+
# @param device_id [String]
|
|
66
|
+
# @param body [Hash]
|
|
67
|
+
# @return [Hash]
|
|
68
|
+
def post_route(route, device_id, body)
|
|
69
|
+
resp = Http.post(
|
|
70
|
+
"#{API_URL}?route=#{route}",
|
|
71
|
+
headers: build_headers(device_id),
|
|
72
|
+
body: JSON.generate(body),
|
|
73
|
+
timeout: 15
|
|
74
|
+
)
|
|
75
|
+
resp.raise_for_status
|
|
76
|
+
decode_payload(resp.body)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# 创建 tempgbox.net 临时邮箱
|
|
80
|
+
# @return [EmailInfo]
|
|
81
|
+
def generate_email
|
|
82
|
+
device_id = random_device_id
|
|
83
|
+
payload = post_route("generate", device_id, "variant" => "googlemail")
|
|
84
|
+
alias_data = payload["alias"]
|
|
85
|
+
raise "tempgbox: 响应缺少 alias" unless alias_data.is_a?(Hash)
|
|
86
|
+
|
|
87
|
+
email = alias_data["email"].to_s.strip
|
|
88
|
+
email = alias_data["alias"].to_s.strip if email.empty?
|
|
89
|
+
raise "tempgbox: 响应缺少 email" if email.empty?
|
|
90
|
+
|
|
91
|
+
EmailInfo.new(channel: CHANNEL, email: email, token: device_id)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# 获取 tempgbox.net 邮件列表
|
|
95
|
+
# @param device_id [String] token
|
|
96
|
+
# @param email [String]
|
|
97
|
+
# @return [Array<Email>]
|
|
98
|
+
def get_emails(device_id, email)
|
|
99
|
+
raise "tempgbox: missing device id" if device_id.to_s.strip.empty?
|
|
100
|
+
|
|
101
|
+
payload = post_route("inbox", device_id, "email" => email)
|
|
102
|
+
messages = Array(payload["messages"])
|
|
103
|
+
|
|
104
|
+
messages.filter_map do |raw|
|
|
105
|
+
next unless raw.is_a?(Hash)
|
|
106
|
+
|
|
107
|
+
raw["from"] ||= raw["sender"]
|
|
108
|
+
raw["text"] ||= raw["body_text"]
|
|
109
|
+
raw["html"] ||= raw["body_html"]
|
|
110
|
+
raw["date"] ||= raw["received_at"]
|
|
111
|
+
raw["messageId"] ||= raw["message_id"]
|
|
112
|
+
raw["attachments"] ||= raw["attachments_info"]
|
|
113
|
+
Normalize.normalize_email(raw.merge("to" => email), email)
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "uri"
|
|
4
|
+
require "json"
|
|
5
|
+
|
|
6
|
+
module TempmailSdk
|
|
7
|
+
module Providers
|
|
8
|
+
# tempmail.cn 渠道实现(HTTP API 模式)
|
|
9
|
+
#
|
|
10
|
+
# 流程:WebSocket 创建邮箱(request mailbox 事件),获取 shortID
|
|
11
|
+
# GET /api/mails/{email} 获取邮件列表(HTTP API)
|
|
12
|
+
# Ruby 端使用 HTTP API 拉取邮件,不实现 WebSocket 推送
|
|
13
|
+
# token 不需要(无状态 HTTP API)
|
|
14
|
+
module TempmailCn
|
|
15
|
+
CHANNEL = "tempmail-cn"
|
|
16
|
+
DEFAULT_HOST = "tempmail.cn"
|
|
17
|
+
|
|
18
|
+
module_function
|
|
19
|
+
|
|
20
|
+
# 规范化 host(去除协议前缀、路径、@ 符号等)
|
|
21
|
+
# @param domain [String, nil]
|
|
22
|
+
# @return [String]
|
|
23
|
+
def normalize_host(domain)
|
|
24
|
+
return DEFAULT_HOST if domain.nil? || domain.to_s.strip.empty?
|
|
25
|
+
|
|
26
|
+
host = domain.to_s.strip
|
|
27
|
+
if host.start_with?("http://", "https://")
|
|
28
|
+
begin
|
|
29
|
+
uri = URI.parse(host)
|
|
30
|
+
host = uri.host.to_s unless uri.host.to_s.empty?
|
|
31
|
+
rescue URI::InvalidURIError
|
|
32
|
+
nil
|
|
33
|
+
end
|
|
34
|
+
elsif host.include?("/")
|
|
35
|
+
host = host.split("/").first.to_s
|
|
36
|
+
end
|
|
37
|
+
host = host.split("@").last.to_s if host.include?("@")
|
|
38
|
+
host = host.strip.gsub(/\A\.+|\.+\z/, "")
|
|
39
|
+
host.empty? ? DEFAULT_HOST : host
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# 生成随机本地部分(小写字母 + 数字)
|
|
43
|
+
# @param length [Integer]
|
|
44
|
+
# @return [String]
|
|
45
|
+
def random_local(length = 10)
|
|
46
|
+
chars = ("a".."z").to_a + ("0".."9").to_a
|
|
47
|
+
Array.new(length) { chars.sample }.join
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# 创建 tempmail.cn 临时邮箱
|
|
51
|
+
# @param domain [String, nil]
|
|
52
|
+
# @return [EmailInfo]
|
|
53
|
+
def generate_email(domain = nil)
|
|
54
|
+
host = normalize_host(domain)
|
|
55
|
+
# 直接生成随机邮箱(HTTP 模式,不走 WebSocket)
|
|
56
|
+
email = "#{random_local}@#{host}"
|
|
57
|
+
EmailInfo.new(channel: CHANNEL, email: email)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# 获取 tempmail.cn 邮件列表
|
|
61
|
+
# @param email [String]
|
|
62
|
+
# @return [Array<Email>]
|
|
63
|
+
def get_emails(email)
|
|
64
|
+
email = email.to_s.strip
|
|
65
|
+
raise "tempmail-cn: 邮箱地址为空" if email.empty?
|
|
66
|
+
|
|
67
|
+
at_idx = email.index("@")
|
|
68
|
+
host = at_idx ? email[(at_idx + 1)..] : DEFAULT_HOST
|
|
69
|
+
host = normalize_host(host)
|
|
70
|
+
|
|
71
|
+
api_url = "https://#{host}/api/mails/#{URI.encode_uri_component(email)}"
|
|
72
|
+
hdrs = {
|
|
73
|
+
"User-Agent" => "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " \
|
|
74
|
+
"(KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36 Edg/146.0.0.0",
|
|
75
|
+
"Accept" => "application/json",
|
|
76
|
+
"Accept-Language" => "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
|
|
77
|
+
"Cache-Control" => "no-cache",
|
|
78
|
+
"DNT" => "1",
|
|
79
|
+
"Pragma" => "no-cache",
|
|
80
|
+
"Referer" => "https://#{host}/"
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
resp = Http.get(api_url, headers: hdrs, timeout: 15)
|
|
84
|
+
resp.raise_for_status
|
|
85
|
+
|
|
86
|
+
data = resp.json
|
|
87
|
+
mails = data.is_a?(Hash) ? Array(data["mails"]) : []
|
|
88
|
+
|
|
89
|
+
mails.filter_map do |raw|
|
|
90
|
+
next unless raw.is_a?(Hash)
|
|
91
|
+
|
|
92
|
+
id = raw["id"].to_s
|
|
93
|
+
id = raw["_id"].to_s if id.empty?
|
|
94
|
+
flat = {
|
|
95
|
+
"id" => id,
|
|
96
|
+
"from" => raw["from"].to_s,
|
|
97
|
+
"to" => email,
|
|
98
|
+
"subject" => raw["subject"].to_s,
|
|
99
|
+
"text" => raw["text"].to_s,
|
|
100
|
+
"html" => raw["html"].to_s,
|
|
101
|
+
"date" => raw["date"].to_s,
|
|
102
|
+
"isRead" => false,
|
|
103
|
+
"attachments" => raw["attachments"]
|
|
104
|
+
}
|
|
105
|
+
flat["text"] = raw["body"].to_s if flat["text"].empty?
|
|
106
|
+
Normalize.normalize_email(flat, email)
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|