tempmail_sdk 1.3.3
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 +7 -0
- data/README.md +116 -0
- data/lib/tempmail_sdk/channels.rb +289 -0
- data/lib/tempmail_sdk/channels_data.rb +289 -0
- data/lib/tempmail_sdk/client.rb +193 -0
- data/lib/tempmail_sdk/config.rb +104 -0
- data/lib/tempmail_sdk/html_utils.rb +42 -0
- data/lib/tempmail_sdk/http.rb +225 -0
- data/lib/tempmail_sdk/logger.rb +38 -0
- data/lib/tempmail_sdk/normalize.rb +193 -0
- data/lib/tempmail_sdk/providers/anonbox.rb +248 -0
- data/lib/tempmail_sdk/providers/anonymmail.rb +92 -0
- data/lib/tempmail_sdk/providers/byom.rb +48 -0
- data/lib/tempmail_sdk/providers/catchmail.rb +110 -0
- data/lib/tempmail_sdk/providers/cleantempmail.rb +49 -0
- data/lib/tempmail_sdk/providers/devmail_uk.rb +58 -0
- data/lib/tempmail_sdk/providers/dropmail.rb +133 -0
- data/lib/tempmail_sdk/providers/dropmail_click.rb +81 -0
- data/lib/tempmail_sdk/providers/dropmail_me.rb +163 -0
- data/lib/tempmail_sdk/providers/duckmail.rb +63 -0
- data/lib/tempmail_sdk/providers/eyepaste.rb +119 -0
- data/lib/tempmail_sdk/providers/fake_email_site.rb +48 -0
- data/lib/tempmail_sdk/providers/fake_legal.rb +109 -0
- data/lib/tempmail_sdk/providers/fmail.rb +87 -0
- data/lib/tempmail_sdk/providers/freecustom.rb +135 -0
- data/lib/tempmail_sdk/providers/getnada.rb +146 -0
- data/lib/tempmail_sdk/providers/gonebox_email.rb +89 -0
- data/lib/tempmail_sdk/providers/guerrillamail.rb +62 -0
- data/lib/tempmail_sdk/providers/guerrillamail_mirrors.rb +81 -0
- data/lib/tempmail_sdk/providers/harakirimail.rb +84 -0
- data/lib/tempmail_sdk/providers/haribu.rb +176 -0
- data/lib/tempmail_sdk/providers/inboxes.rb +110 -0
- data/lib/tempmail_sdk/providers/inboxkitten.rb +90 -0
- data/lib/tempmail_sdk/providers/linshiyou.rb +157 -0
- data/lib/tempmail_sdk/providers/lroid.rb +170 -0
- data/lib/tempmail_sdk/providers/m2u.rb +126 -0
- data/lib/tempmail_sdk/providers/mail10s.rb +59 -0
- data/lib/tempmail_sdk/providers/mail123.rb +82 -0
- data/lib/tempmail_sdk/providers/mail_cx.rb +133 -0
- data/lib/tempmail_sdk/providers/mail_sunls.rb +68 -0
- data/lib/tempmail_sdk/providers/mail_tm.rb +99 -0
- data/lib/tempmail_sdk/providers/mailcatch.rb +91 -0
- data/lib/tempmail_sdk/providers/maildrop.rb +92 -0
- data/lib/tempmail_sdk/providers/maildrop_cc.rb +110 -0
- data/lib/tempmail_sdk/providers/mailforspam.rb +96 -0
- data/lib/tempmail_sdk/providers/mailhole_de.rb +70 -0
- data/lib/tempmail_sdk/providers/mailinator.rb +182 -0
- data/lib/tempmail_sdk/providers/mailmomy.rb +108 -0
- data/lib/tempmail_sdk/providers/mailnesia.rb +141 -0
- data/lib/tempmail_sdk/providers/mffac.rb +101 -0
- data/lib/tempmail_sdk/providers/moakt.rb +296 -0
- data/lib/tempmail_sdk/providers/neighbours.rb +140 -0
- data/lib/tempmail_sdk/providers/neighbours_sh.rb +117 -0
- data/lib/tempmail_sdk/providers/nimail.rb +100 -0
- data/lib/tempmail_sdk/providers/ockito.rb +163 -0
- data/lib/tempmail_sdk/providers/one_sec_mail.rb +46 -0
- data/lib/tempmail_sdk/providers/restmail_net.rb +60 -0
- data/lib/tempmail_sdk/providers/rootsh.rb +167 -0
- data/lib/tempmail_sdk/providers/shitty_email.rb +84 -0
- data/lib/tempmail_sdk/providers/smailpro.rb +103 -0
- data/lib/tempmail_sdk/providers/ta_easy.rb +53 -0
- data/lib/tempmail_sdk/providers/temp_mail_io.rb +67 -0
- data/lib/tempmail_sdk/providers/temp_mail_now.rb +103 -0
- data/lib/tempmail_sdk/providers/temp_mail_org.rb +89 -0
- data/lib/tempmail_sdk/providers/tempemail_co.rb +142 -0
- data/lib/tempmail_sdk/providers/tempemail_info.rb +121 -0
- data/lib/tempmail_sdk/providers/tempemails_net.rb +159 -0
- data/lib/tempmail_sdk/providers/tempfastmail.rb +76 -0
- data/lib/tempmail_sdk/providers/tempgmailer.rb +126 -0
- data/lib/tempmail_sdk/providers/tempgo_email.rb +74 -0
- data/lib/tempmail_sdk/providers/tempinbox.rb +70 -0
- data/lib/tempmail_sdk/providers/tempmail.rb +66 -0
- data/lib/tempmail_sdk/providers/tempmail365.rb +119 -0
- data/lib/tempmail_sdk/providers/tempmail_fish.rb +89 -0
- data/lib/tempmail_sdk/providers/tempmail_fyi.rb +124 -0
- data/lib/tempmail_sdk/providers/tempmail_lol.rb +42 -0
- data/lib/tempmail_sdk/providers/tempmail_lol_v2.rb +57 -0
- data/lib/tempmail_sdk/providers/tempmail_plus.rb +80 -0
- data/lib/tempmail_sdk/providers/tempmailc.rb +80 -0
- data/lib/tempmail_sdk/providers/tempmailpro.rb +89 -0
- data/lib/tempmail_sdk/providers/tempmailten.rb +150 -0
- data/lib/tempmail_sdk/providers/temporam.rb +95 -0
- data/lib/tempmail_sdk/providers/tempp_mails.rb +150 -0
- data/lib/tempmail_sdk/providers/tempy_email.rb +73 -0
- data/lib/tempmail_sdk/providers/ten_minute_mail_net.rb +125 -0
- data/lib/tempmail_sdk/providers/tenminute_one.rb +255 -0
- data/lib/tempmail_sdk/providers/throwawaymail.rb +80 -0
- data/lib/tempmail_sdk/providers/tmail_link.rb +98 -0
- data/lib/tempmail_sdk/providers/uncorreotemporal.rb +84 -0
- data/lib/tempmail_sdk/providers/webmailtemp.rb +108 -0
- data/lib/tempmail_sdk/providers/xkx_me.rb +125 -0
- data/lib/tempmail_sdk/providers/zhujump.rb +256 -0
- data/lib/tempmail_sdk/registry.rb +1097 -0
- data/lib/tempmail_sdk/retry.rb +91 -0
- data/lib/tempmail_sdk/telemetry.rb +134 -0
- data/lib/tempmail_sdk/types.rb +164 -0
- data/lib/tempmail_sdk/version.rb +6 -0
- data/lib/tempmail_sdk/webui.rb +360 -0
- data/lib/tempmail_sdk.rb +76 -0
- metadata +142 -0
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module TempmailSdk
|
|
4
|
+
module Providers
|
|
5
|
+
# freecustom.email 渠道实现
|
|
6
|
+
# 免注册临时邮箱,任意 local part @ 可用域名即时可收信
|
|
7
|
+
module Freecustom
|
|
8
|
+
CHANNEL = "freecustom"
|
|
9
|
+
SITE_URL = "https://www.freecustom.email"
|
|
10
|
+
DOMAINS_URL = "https://api2.freecustom.email/domains"
|
|
11
|
+
REFERER = "https://www.freecustom.email/en"
|
|
12
|
+
DEFAULT_UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " \
|
|
13
|
+
"(KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36"
|
|
14
|
+
|
|
15
|
+
module_function
|
|
16
|
+
|
|
17
|
+
def get_ua
|
|
18
|
+
cfg = Config.get_config
|
|
19
|
+
(cfg.headers || {})["User-Agent"] || DEFAULT_UA
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def random_local(n = 10)
|
|
23
|
+
chars = ("a".."z").to_a + ("0".."9").to_a
|
|
24
|
+
Array.new(n) { chars.sample }.join
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def first_val(msg, *keys)
|
|
28
|
+
keys.each do |key|
|
|
29
|
+
val = msg[key]
|
|
30
|
+
return val.to_s if !val.nil? && val != ""
|
|
31
|
+
end
|
|
32
|
+
""
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# 挑选一个可收信域名
|
|
36
|
+
def pick_domain
|
|
37
|
+
resp = Http.get(
|
|
38
|
+
DOMAINS_URL,
|
|
39
|
+
headers: { "User-Agent" => get_ua, "Accept" => "application/json", "Referer" => REFERER }
|
|
40
|
+
)
|
|
41
|
+
resp.raise_for_status
|
|
42
|
+
data = resp.json
|
|
43
|
+
lst = data.is_a?(Hash) ? data["data"] : nil
|
|
44
|
+
raise "freecustom: domain list empty" unless lst.is_a?(Array) && !lst.empty?
|
|
45
|
+
|
|
46
|
+
usable = lst.select do |d|
|
|
47
|
+
d.is_a?(Hash) && d["tier"] == "free" && d["expiring_soon"] != true && !d["domain"].to_s.empty?
|
|
48
|
+
end
|
|
49
|
+
pool = usable.empty? ? lst.select { |d| d.is_a?(Hash) && !d["domain"].to_s.empty? } : usable
|
|
50
|
+
raise "freecustom: no usable domain" if pool.empty?
|
|
51
|
+
|
|
52
|
+
pool.sample["domain"].to_s
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# 获取匿名访问令牌
|
|
56
|
+
def fetch_auth_token
|
|
57
|
+
resp = Http.post(
|
|
58
|
+
"#{SITE_URL}/api/auth",
|
|
59
|
+
headers: {
|
|
60
|
+
"User-Agent" => get_ua, "Accept" => "application/json",
|
|
61
|
+
"Content-Type" => "application/json", "Referer" => REFERER
|
|
62
|
+
}
|
|
63
|
+
)
|
|
64
|
+
resp.raise_for_status
|
|
65
|
+
data = resp.json
|
|
66
|
+
raise "freecustom: invalid token response" unless data.is_a?(Hash) && !data["token"].to_s.empty?
|
|
67
|
+
|
|
68
|
+
data["token"].to_s
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# @return [EmailInfo]
|
|
72
|
+
def generate_email
|
|
73
|
+
domain = pick_domain
|
|
74
|
+
email = "#{random_local}@#{domain}"
|
|
75
|
+
EmailInfo.new(channel: CHANNEL, email: email, token: email)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# @param email [String]
|
|
79
|
+
# @return [Array<Email>]
|
|
80
|
+
def get_emails(email)
|
|
81
|
+
addr = email.to_s.strip
|
|
82
|
+
raise "freecustom: missing email" if addr.empty?
|
|
83
|
+
|
|
84
|
+
jwt = fetch_auth_token
|
|
85
|
+
auth_headers = {
|
|
86
|
+
"User-Agent" => get_ua, "Accept" => "application/json",
|
|
87
|
+
"Referer" => REFERER, "Authorization" => "Bearer #{jwt}",
|
|
88
|
+
"x-fce-client" => "web-client"
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
list_resp = Http.get(
|
|
92
|
+
"#{SITE_URL}/api/public-mailbox?fullMailboxId=#{URI.encode_www_form_component(addr)}",
|
|
93
|
+
headers: auth_headers
|
|
94
|
+
)
|
|
95
|
+
list_resp.raise_for_status
|
|
96
|
+
list_data = list_resp.json
|
|
97
|
+
return [] unless list_data.is_a?(Hash) && list_data["success"]
|
|
98
|
+
|
|
99
|
+
items = list_data["data"]
|
|
100
|
+
return [] unless items.is_a?(Array)
|
|
101
|
+
|
|
102
|
+
items.select { |i| i.is_a?(Hash) && i["id"] }.map do |item|
|
|
103
|
+
msg_id = item["id"].to_s
|
|
104
|
+
full = item
|
|
105
|
+
begin
|
|
106
|
+
msg_resp = Http.get(
|
|
107
|
+
"#{SITE_URL}/api/public-mailbox?fullMailboxId=#{URI.encode_www_form_component(addr)}" \
|
|
108
|
+
"&messageId=#{URI.encode_www_form_component(msg_id)}",
|
|
109
|
+
headers: auth_headers
|
|
110
|
+
)
|
|
111
|
+
if msg_resp.ok?
|
|
112
|
+
msg_data = msg_resp.json
|
|
113
|
+
if msg_data.is_a?(Hash) && msg_data["success"] && msg_data["data"].is_a?(Hash)
|
|
114
|
+
full = msg_data["data"]
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
rescue StandardError
|
|
118
|
+
# 正文补全失败时退回列表元数据
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
row = {
|
|
122
|
+
"id" => first_val(full, "id"),
|
|
123
|
+
"from" => first_val(full, "from"),
|
|
124
|
+
"to" => first_val(full, "to").empty? ? addr : first_val(full, "to"),
|
|
125
|
+
"subject" => first_val(full, "subject"),
|
|
126
|
+
"text" => first_val(full, "text"),
|
|
127
|
+
"html" => first_val(full, "html"),
|
|
128
|
+
"date" => first_val(full, "date")
|
|
129
|
+
}
|
|
130
|
+
Normalize.normalize_email(row, addr)
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "securerandom"
|
|
4
|
+
require "uri"
|
|
5
|
+
|
|
6
|
+
module TempmailSdk
|
|
7
|
+
module Providers
|
|
8
|
+
# GetNada 渠道实现 -- https://getnada.net
|
|
9
|
+
# 匿名 JSON API:拉取公开域名 -> 开信箱换取 token -> 拉取邮件列表/详情
|
|
10
|
+
module Getnada
|
|
11
|
+
CHANNEL = "getnada"
|
|
12
|
+
API_BASE = "https://getnada.net/api"
|
|
13
|
+
HEADERS_JSON = { "Accept" => "application/json" }.freeze
|
|
14
|
+
HEADERS_POST = { "Accept" => "application/json", "Content-Type" => "application/json" }.freeze
|
|
15
|
+
# 单个域名标签校验(大小写不敏感)
|
|
16
|
+
DOMAIN_LABEL_RE = /\A[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\z/i.freeze
|
|
17
|
+
|
|
18
|
+
module_function
|
|
19
|
+
|
|
20
|
+
# 生成随机本地部分:sdk + 16 位小写字母数字
|
|
21
|
+
def random_local
|
|
22
|
+
chars = ("a".."z").to_a + ("0".."9").to_a
|
|
23
|
+
"sdk" + Array.new(16) { chars.sample }.join
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# 清洗并校验域名,非法返回空串
|
|
27
|
+
def clean_domain(value)
|
|
28
|
+
domain = value.to_s.strip.downcase
|
|
29
|
+
return "" if domain.empty? || domain.length > 253 || domain.include?("..")
|
|
30
|
+
|
|
31
|
+
labels = domain.split(".")
|
|
32
|
+
return "" if labels.length < 2
|
|
33
|
+
return "" unless labels.all? { |label| DOMAIN_LABEL_RE.match?(label) }
|
|
34
|
+
|
|
35
|
+
domain
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# 拉取公开域名列表,按偏好挑选,缺省优先 getnada.net
|
|
39
|
+
# @param preferred [String, nil]
|
|
40
|
+
# @return [String]
|
|
41
|
+
def pick_domain(preferred = nil)
|
|
42
|
+
resp = Http.get("#{API_BASE}/public/domains", headers: HEADERS_JSON, timeout: 15)
|
|
43
|
+
resp.raise_for_status
|
|
44
|
+
data = resp.json
|
|
45
|
+
domains = data.is_a?(Hash) ? data["domains"] : []
|
|
46
|
+
domains = [] unless domains.is_a?(Array)
|
|
47
|
+
cleaned = domains.map { |d| clean_domain(d) }.reject(&:empty?)
|
|
48
|
+
|
|
49
|
+
wanted = preferred.to_s.strip.sub(/\A@/, "").downcase
|
|
50
|
+
unless wanted.empty?
|
|
51
|
+
found = cleaned.find { |d| d == wanted }
|
|
52
|
+
return found if found
|
|
53
|
+
|
|
54
|
+
raise "getnada: domain not available: #{wanted}"
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
default = cleaned.find { |d| d == "getnada.net" }
|
|
58
|
+
return default if default
|
|
59
|
+
return cleaned.first unless cleaned.empty?
|
|
60
|
+
|
|
61
|
+
raise "getnada: no domain available"
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# 将原始邮件对象展平为 normalize 可识别的字段
|
|
65
|
+
def flatten_message(raw, recipient)
|
|
66
|
+
out = raw.dup
|
|
67
|
+
out["id"] = raw["id"]
|
|
68
|
+
out["from"] = first_non_empty(raw["from_addr"], raw["from"])
|
|
69
|
+
out["to"] = first_non_empty(raw["to"]).empty? ? recipient : raw["to"]
|
|
70
|
+
out["text"] = first_non_empty(raw["text_plain"], raw["text"])
|
|
71
|
+
out["html"] = first_non_empty(raw["html_sanitized"], raw["html"])
|
|
72
|
+
out["read"] = !raw["read_at"].to_s.empty? if raw.key?("read_at")
|
|
73
|
+
out
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def first_non_empty(*values)
|
|
77
|
+
values.each do |v|
|
|
78
|
+
next if v.nil?
|
|
79
|
+
|
|
80
|
+
t = v.to_s
|
|
81
|
+
return t unless t.empty?
|
|
82
|
+
end
|
|
83
|
+
""
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# 生成临时邮箱:选域名 -> 开信箱 -> 取 token/recipient
|
|
87
|
+
# @param domain [String, nil] 指定域名(多渠道别名派生用)
|
|
88
|
+
# @param channel [String] 渠道标识
|
|
89
|
+
# @return [EmailInfo]
|
|
90
|
+
def generate_email(domain = nil, channel = CHANNEL)
|
|
91
|
+
selected_domain = pick_domain(domain)
|
|
92
|
+
requested = "#{random_local}@#{selected_domain}"
|
|
93
|
+
resp = Http.post("#{API_BASE}/inbox/open", headers: HEADERS_POST,
|
|
94
|
+
json: { "email" => requested }, timeout: 15)
|
|
95
|
+
resp.raise_for_status
|
|
96
|
+
data = resp.json
|
|
97
|
+
raise "getnada: invalid open response" unless data.is_a?(Hash)
|
|
98
|
+
|
|
99
|
+
token = data["token"].to_s.strip
|
|
100
|
+
email = (data["recipient"] || requested).to_s.strip
|
|
101
|
+
raise "getnada: invalid open response" if token.empty? || email.empty? || !email.include?("@")
|
|
102
|
+
|
|
103
|
+
EmailInfo.new(channel: channel, email: email, token: token, expires_at: data["activeUntil"])
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# 拉取单封邮件详情,失败返回 nil
|
|
107
|
+
def fetch_detail(token, message_id)
|
|
108
|
+
url = "#{API_BASE}/inbox/message?id=#{URI.encode_www_form_component(message_id)}" \
|
|
109
|
+
"&token=#{URI.encode_www_form_component(token)}"
|
|
110
|
+
resp = Http.get(url, headers: HEADERS_JSON, timeout: 15)
|
|
111
|
+
return nil unless resp.ok?
|
|
112
|
+
|
|
113
|
+
data = resp.json
|
|
114
|
+
return data["message"] if data.is_a?(Hash) && data["message"].is_a?(Hash)
|
|
115
|
+
|
|
116
|
+
nil
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# 拉取邮件列表并逐封获取详情
|
|
120
|
+
# @param token [String]
|
|
121
|
+
# @param email [String]
|
|
122
|
+
# @return [Array<Email>]
|
|
123
|
+
def get_emails(token, email)
|
|
124
|
+
auth = token.to_s.strip
|
|
125
|
+
address = email.to_s.strip
|
|
126
|
+
raise "getnada: empty token" if auth.empty?
|
|
127
|
+
raise "getnada: empty email" if address.empty?
|
|
128
|
+
|
|
129
|
+
resp = Http.get("#{API_BASE}/inbox/messages?token=#{URI.encode_www_form_component(auth)}",
|
|
130
|
+
headers: HEADERS_JSON, timeout: 15)
|
|
131
|
+
resp.raise_for_status
|
|
132
|
+
data = resp.json
|
|
133
|
+
rows = data.is_a?(Hash) ? data["messages"] : []
|
|
134
|
+
return [] unless rows.is_a?(Array)
|
|
135
|
+
|
|
136
|
+
rows.filter_map do |item|
|
|
137
|
+
next unless item.is_a?(Hash)
|
|
138
|
+
|
|
139
|
+
message_id = item["id"].to_s.strip
|
|
140
|
+
detail = message_id.empty? ? nil : fetch_detail(auth, message_id)
|
|
141
|
+
Normalize.normalize_email(flatten_message(detail || item, address), address)
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module TempmailSdk
|
|
4
|
+
module Providers
|
|
5
|
+
# gonebox.email 渠道实现
|
|
6
|
+
# 一次性临时邮箱服务,无需认证
|
|
7
|
+
module GoneboxEmail
|
|
8
|
+
CHANNEL = "gonebox-email"
|
|
9
|
+
BASE_URL = "https://api.gonebox.email/api/v1"
|
|
10
|
+
HEADERS = {
|
|
11
|
+
"User-Agent" => "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " \
|
|
12
|
+
"(KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36",
|
|
13
|
+
"Accept" => "application/json",
|
|
14
|
+
"Content-Type" => "application/json"
|
|
15
|
+
}.freeze
|
|
16
|
+
|
|
17
|
+
module_function
|
|
18
|
+
|
|
19
|
+
# @return [EmailInfo]
|
|
20
|
+
def generate_email
|
|
21
|
+
resp = Http.post(
|
|
22
|
+
"#{BASE_URL}/inboxes",
|
|
23
|
+
headers: HEADERS,
|
|
24
|
+
json: { "domain" => "gonebox.email" },
|
|
25
|
+
timeout: 15
|
|
26
|
+
)
|
|
27
|
+
resp.raise_for_status
|
|
28
|
+
body = resp.json
|
|
29
|
+
raise "gonebox-email: create failed" unless body.is_a?(Hash) && body["success"]
|
|
30
|
+
|
|
31
|
+
data = body["data"]
|
|
32
|
+
raise "gonebox-email: invalid data" unless data.is_a?(Hash)
|
|
33
|
+
|
|
34
|
+
address = data["address"].to_s
|
|
35
|
+
raise "gonebox-email: missing address" if address.empty?
|
|
36
|
+
|
|
37
|
+
# expiresAt 为秒级时间戳,转为毫秒
|
|
38
|
+
expires_at = nil
|
|
39
|
+
raw_expires = data["expiresAt"]
|
|
40
|
+
if raw_expires
|
|
41
|
+
begin
|
|
42
|
+
expires_at = raw_expires.to_i * 1000
|
|
43
|
+
rescue StandardError
|
|
44
|
+
# 忽略
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
EmailInfo.new(channel: CHANNEL, email: address, token: "", expires_at: expires_at)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# @param token [String]
|
|
52
|
+
# @param email [String]
|
|
53
|
+
# @return [Array<Email>]
|
|
54
|
+
def get_emails(token, email)
|
|
55
|
+
address = email.to_s.strip
|
|
56
|
+
raise "gonebox-email: missing email" if address.empty?
|
|
57
|
+
|
|
58
|
+
headers = HEADERS.reject { |k, _| k == "Content-Type" }
|
|
59
|
+
resp = Http.get("#{BASE_URL}/inboxes/#{address}/messages", headers: headers, timeout: 15)
|
|
60
|
+
return [] if resp.status_code == 404
|
|
61
|
+
|
|
62
|
+
resp.raise_for_status
|
|
63
|
+
body = resp.json
|
|
64
|
+
return [] unless body.is_a?(Hash) && body["success"]
|
|
65
|
+
|
|
66
|
+
data = body["data"]
|
|
67
|
+
return [] unless data.is_a?(Hash)
|
|
68
|
+
|
|
69
|
+
messages = data["messages"]
|
|
70
|
+
return [] unless messages.is_a?(Array) && !messages.empty?
|
|
71
|
+
|
|
72
|
+
messages.select { |m| m.is_a?(Hash) }.map do |msg|
|
|
73
|
+
raw = {
|
|
74
|
+
"id" => msg["id"].to_s,
|
|
75
|
+
"from" => (msg["from"] || msg["sender"]).to_s,
|
|
76
|
+
"to" => address,
|
|
77
|
+
"subject" => msg["subject"].to_s,
|
|
78
|
+
"text" => (msg["text"] || msg["body_plain"]).to_s,
|
|
79
|
+
"html" => (msg["html"] || msg["body_html"]).to_s,
|
|
80
|
+
"date" => (msg["date"] || msg["received_at"]).to_s,
|
|
81
|
+
"is_read" => false,
|
|
82
|
+
"attachments" => []
|
|
83
|
+
}
|
|
84
|
+
Normalize.normalize_email(raw, address)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module TempmailSdk
|
|
4
|
+
module Providers
|
|
5
|
+
# guerrillamail.com 渠道实现
|
|
6
|
+
# API: https://api.guerrillamail.com/ajax.php
|
|
7
|
+
module Guerrillamail
|
|
8
|
+
CHANNEL = "guerrillamail"
|
|
9
|
+
BASE_URL = "https://api.guerrillamail.com/ajax.php"
|
|
10
|
+
DEFAULT_HEADERS = {
|
|
11
|
+
"User-Agent" => "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
|
|
12
|
+
}.freeze
|
|
13
|
+
|
|
14
|
+
module_function
|
|
15
|
+
|
|
16
|
+
# @return [EmailInfo]
|
|
17
|
+
def generate_email
|
|
18
|
+
resp = Http.get("#{BASE_URL}?f=get_email_address&lang=en", headers: DEFAULT_HEADERS)
|
|
19
|
+
resp.raise_for_status
|
|
20
|
+
data = resp.json
|
|
21
|
+
if data["email_addr"].to_s.empty? || data["sid_token"].to_s.empty?
|
|
22
|
+
raise "Failed to generate email: missing email_addr or sid_token"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
expires_at = nil
|
|
26
|
+
expires_at = (data["email_timestamp"] + 3600) * 1000 if data["email_timestamp"]
|
|
27
|
+
EmailInfo.new(channel: CHANNEL, email: data["email_addr"], token: data["sid_token"], expires_at: expires_at)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# @param email [String]
|
|
31
|
+
# @param token [String]
|
|
32
|
+
# @return [Array<Email>]
|
|
33
|
+
def get_emails(email, token)
|
|
34
|
+
sid = URI.encode_www_form_component(token)
|
|
35
|
+
resp = Http.get("#{BASE_URL}?f=check_email&seq=0&sid_token=#{sid}", headers: DEFAULT_HEADERS)
|
|
36
|
+
resp.raise_for_status
|
|
37
|
+
list = resp.json["list"]
|
|
38
|
+
return [] unless list.is_a?(Array)
|
|
39
|
+
|
|
40
|
+
list.map do |item|
|
|
41
|
+
body = item["mail_body"].to_s
|
|
42
|
+
if body.empty? && item["mail_id"]
|
|
43
|
+
begin
|
|
44
|
+
mid = URI.encode_www_form_component(item["mail_id"].to_s)
|
|
45
|
+
dr = Http.get("#{BASE_URL}?f=fetch_email&sid_token=#{sid}&email_id=#{mid}", headers: DEFAULT_HEADERS)
|
|
46
|
+
body = dr.json["mail_body"].to_s if dr.ok?
|
|
47
|
+
rescue StandardError
|
|
48
|
+
# 忽略单封详情失败
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
text = item["mail_excerpt"].to_s
|
|
52
|
+
text = body.gsub(/<[^>]+>/, " ").split.join(" ") if text.empty?
|
|
53
|
+
Normalize.normalize_email({
|
|
54
|
+
"id" => item["mail_id"], "from" => item["mail_from"], "to" => email,
|
|
55
|
+
"subject" => item["mail_subject"], "text" => text, "html" => body,
|
|
56
|
+
"date" => item["mail_date"] || "", "isRead" => item["mail_read"] == 1
|
|
57
|
+
}, email)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module TempmailSdk
|
|
4
|
+
module Providers
|
|
5
|
+
# Guerrillamail 镜像渠道实现
|
|
6
|
+
# sharklasers.com / grr.la / guerrillamail.info / spam4.me 等共用同一套 ajax.php API,仅 baseURL 不同
|
|
7
|
+
module GuerrillamailMirrors
|
|
8
|
+
DEFAULT_HEADERS = {
|
|
9
|
+
"User-Agent" => "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
|
|
10
|
+
}.freeze
|
|
11
|
+
|
|
12
|
+
# 各镜像渠道的固定 baseURL 表
|
|
13
|
+
BASE_URLS = {
|
|
14
|
+
"sharklasers" => "https://www.sharklasers.com/ajax.php",
|
|
15
|
+
"grr-la" => "https://www.grr.la/ajax.php",
|
|
16
|
+
"guerrillamail-info" => "https://www.guerrillamail.info/ajax.php",
|
|
17
|
+
"spam4me" => "https://www.spam4.me/ajax.php",
|
|
18
|
+
"guerrillamail-com" => "https://guerrillamail.com/ajax.php",
|
|
19
|
+
"sharklasers-com" => "https://sharklasers.com/ajax.php",
|
|
20
|
+
"grr-la-com" => "https://grr.la/ajax.php",
|
|
21
|
+
"guerrillamail-net" => "https://www.guerrillamail.net/ajax.php",
|
|
22
|
+
"guerrillamail-org" => "https://www.guerrillamail.org/ajax.php",
|
|
23
|
+
"guerrillamailblock" => "https://www.guerrillamailblock.com/ajax.php",
|
|
24
|
+
"guerrillamail-com-www" => "https://www.guerrillamail.com/ajax.php"
|
|
25
|
+
}.freeze
|
|
26
|
+
|
|
27
|
+
module_function
|
|
28
|
+
|
|
29
|
+
# 创建临时邮箱
|
|
30
|
+
# @param channel [String] 渠道标识
|
|
31
|
+
# @return [EmailInfo]
|
|
32
|
+
def generate_email(channel)
|
|
33
|
+
base_url = BASE_URLS.fetch(channel)
|
|
34
|
+
resp = Http.get("#{base_url}?f=get_email_address&lang=en", headers: DEFAULT_HEADERS)
|
|
35
|
+
resp.raise_for_status
|
|
36
|
+
data = resp.json
|
|
37
|
+
if data["email_addr"].to_s.empty? || data["sid_token"].to_s.empty?
|
|
38
|
+
raise "#{channel}: missing email_addr or sid_token"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
expires_at = nil
|
|
42
|
+
expires_at = (data["email_timestamp"] + 3600) * 1000 if data["email_timestamp"]
|
|
43
|
+
EmailInfo.new(channel: channel, email: data["email_addr"], token: data["sid_token"], expires_at: expires_at)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# 获取邮件列表,对无正文的邮件调用 fetch_email 拉取完整正文
|
|
47
|
+
# @param channel [String] 渠道标识
|
|
48
|
+
# @param email [String]
|
|
49
|
+
# @param token [String]
|
|
50
|
+
# @return [Array<Email>]
|
|
51
|
+
def get_emails(channel, email, token)
|
|
52
|
+
base_url = BASE_URLS.fetch(channel)
|
|
53
|
+
sid = URI.encode_www_form_component(token)
|
|
54
|
+
resp = Http.get("#{base_url}?f=check_email&seq=0&sid_token=#{sid}", headers: DEFAULT_HEADERS)
|
|
55
|
+
resp.raise_for_status
|
|
56
|
+
list = resp.json["list"]
|
|
57
|
+
return [] unless list.is_a?(Array)
|
|
58
|
+
|
|
59
|
+
list.map do |item|
|
|
60
|
+
body = item["mail_body"].to_s
|
|
61
|
+
if body.empty? && item["mail_id"]
|
|
62
|
+
begin
|
|
63
|
+
mid = URI.encode_www_form_component(item["mail_id"].to_s)
|
|
64
|
+
dr = Http.get("#{base_url}?f=fetch_email&sid_token=#{sid}&email_id=#{mid}", headers: DEFAULT_HEADERS)
|
|
65
|
+
body = dr.json["mail_body"].to_s if dr.ok?
|
|
66
|
+
rescue StandardError
|
|
67
|
+
# 忽略单封详情失败
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
text = body.empty? ? item["mail_excerpt"].to_s : body.gsub(/<[^>]+>/, " ")
|
|
71
|
+
text = text.split.join(" ")
|
|
72
|
+
Normalize.normalize_email({
|
|
73
|
+
"id" => item["mail_id"], "from" => item["mail_from"], "to" => email,
|
|
74
|
+
"subject" => item["mail_subject"], "text" => text, "html" => body,
|
|
75
|
+
"date" => item["mail_date"] || "", "isRead" => item["mail_read"] == 1
|
|
76
|
+
}, email)
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module TempmailSdk
|
|
4
|
+
module Providers
|
|
5
|
+
# harakirimail.com 渠道实现
|
|
6
|
+
# 无需认证、无需 Cookie、无需 Token 的简单 REST API
|
|
7
|
+
module Harakirimail
|
|
8
|
+
CHANNEL = "harakirimail"
|
|
9
|
+
BASE = "https://harakirimail.com"
|
|
10
|
+
HEADERS = {
|
|
11
|
+
"Accept" => "application/json, text/plain, */*",
|
|
12
|
+
"Accept-Language" => "zh-CN,zh;q=0.9,en;q=0.8",
|
|
13
|
+
"User-Agent" => "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " \
|
|
14
|
+
"(KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36 Edg/146.0.0.0"
|
|
15
|
+
}.freeze
|
|
16
|
+
|
|
17
|
+
module_function
|
|
18
|
+
|
|
19
|
+
def random_name(length = 12)
|
|
20
|
+
chars = ("a".."z").to_a + ("0".."9").to_a
|
|
21
|
+
Array.new(length) { chars.sample }.join
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# @return [EmailInfo]
|
|
25
|
+
def generate_email
|
|
26
|
+
name = random_name
|
|
27
|
+
email = "#{name}@harakirimail.com"
|
|
28
|
+
|
|
29
|
+
# 调用收件箱接口验证地址可用
|
|
30
|
+
resp = Http.get("#{BASE}/api/v1/inbox/#{name}", headers: HEADERS, timeout: 15)
|
|
31
|
+
resp.raise_for_status
|
|
32
|
+
|
|
33
|
+
EmailInfo.new(channel: CHANNEL, email: email)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# 获取单封邮件正文
|
|
37
|
+
def fetch_body(email_id)
|
|
38
|
+
return {} if email_id.to_s.empty?
|
|
39
|
+
|
|
40
|
+
begin
|
|
41
|
+
resp = Http.get("#{BASE}/api/v1/email/#{email_id}", headers: HEADERS, timeout: 15)
|
|
42
|
+
resp.raise_for_status
|
|
43
|
+
data = resp.json
|
|
44
|
+
data.is_a?(Hash) ? data : {}
|
|
45
|
+
rescue StandardError
|
|
46
|
+
{}
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# @param email [String]
|
|
51
|
+
# @return [Array<Email>]
|
|
52
|
+
def get_emails(email)
|
|
53
|
+
e = email.to_s.strip
|
|
54
|
+
raise "harakirimail: empty email" if e.empty?
|
|
55
|
+
|
|
56
|
+
name = e.include?("@") ? e.split("@").first.to_s : e
|
|
57
|
+
|
|
58
|
+
resp = Http.get("#{BASE}/api/v1/inbox/#{name}", headers: HEADERS, timeout: 15)
|
|
59
|
+
resp.raise_for_status
|
|
60
|
+
data = resp.json
|
|
61
|
+
return [] unless data.is_a?(Hash)
|
|
62
|
+
|
|
63
|
+
rows = data["emails"]
|
|
64
|
+
return [] unless rows.is_a?(Array)
|
|
65
|
+
|
|
66
|
+
rows.select { |r| r.is_a?(Hash) }.map do |raw|
|
|
67
|
+
email_id = raw["_id"].to_s
|
|
68
|
+
detail = fetch_body(email_id)
|
|
69
|
+
flat = {
|
|
70
|
+
"id" => email_id,
|
|
71
|
+
"from" => raw["from"].to_s,
|
|
72
|
+
"to" => e,
|
|
73
|
+
"subject" => raw["subject"].to_s,
|
|
74
|
+
"date" => raw["received"].to_s,
|
|
75
|
+
"html" => (detail["body_html"] || detail["html"]).to_s,
|
|
76
|
+
"text" => (detail["body_text"] || detail["text"]).to_s,
|
|
77
|
+
"isRead" => false
|
|
78
|
+
}
|
|
79
|
+
Normalize.normalize_email(flat, e)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|