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,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "uri"
|
|
4
|
+
|
|
5
|
+
module TempmailSdk
|
|
6
|
+
module Providers
|
|
7
|
+
# TempFastMail 渠道 — https://tempfastmail.com
|
|
8
|
+
module Tempfastmail
|
|
9
|
+
CHANNEL = "tempfastmail"
|
|
10
|
+
BASE_URL = "https://tempfastmail.com"
|
|
11
|
+
HEADERS = { "Accept" => "application/json", "User-Agent" => "Mozilla/5.0" }.freeze
|
|
12
|
+
|
|
13
|
+
module_function
|
|
14
|
+
|
|
15
|
+
def flatten(raw, recipient)
|
|
16
|
+
{
|
|
17
|
+
"id" => raw["uuid"],
|
|
18
|
+
"from" => raw["from"],
|
|
19
|
+
"to" => raw["real_to"] || recipient,
|
|
20
|
+
"subject" => raw["subject"],
|
|
21
|
+
"html" => raw["html"],
|
|
22
|
+
"date" => raw["received_at"],
|
|
23
|
+
"attachments" => raw["attachments"]
|
|
24
|
+
}
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# 创建临时邮箱
|
|
28
|
+
# @return [EmailInfo]
|
|
29
|
+
def generate_email
|
|
30
|
+
resp = Http.post("#{BASE_URL}/api/email-box", headers: HEADERS)
|
|
31
|
+
resp.raise_for_status
|
|
32
|
+
data = resp.json
|
|
33
|
+
email = (data["email"] || "").strip
|
|
34
|
+
uuid = (data["uuid"] || "").strip
|
|
35
|
+
raise "tempfastmail: 创建邮箱响应无效" if email.empty? || !email.include?("@") || uuid.empty?
|
|
36
|
+
|
|
37
|
+
EmailInfo.new(channel: CHANNEL, email: email, token: uuid)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# 获取邮件列表
|
|
41
|
+
# @param token [String] UUID
|
|
42
|
+
# @param email [String]
|
|
43
|
+
# @return [Array<Email>]
|
|
44
|
+
def get_emails(token, email)
|
|
45
|
+
uuid = (token || "").strip
|
|
46
|
+
address = (email || "").strip
|
|
47
|
+
raise "tempfastmail: token 为空" if uuid.empty?
|
|
48
|
+
raise "tempfastmail: 邮箱地址为空" if address.empty?
|
|
49
|
+
|
|
50
|
+
resp = Http.get("#{BASE_URL}/api/email-box/#{URI.encode_www_form_component(uuid)}/emails",
|
|
51
|
+
headers: HEADERS)
|
|
52
|
+
resp.raise_for_status
|
|
53
|
+
rows = resp.json
|
|
54
|
+
return [] unless rows.is_a?(Array)
|
|
55
|
+
|
|
56
|
+
rows.filter_map do |row|
|
|
57
|
+
raw = row.is_a?(Hash) ? row : {}
|
|
58
|
+
message_id = (raw["uuid"] || "").strip
|
|
59
|
+
unless message_id.empty?
|
|
60
|
+
begin
|
|
61
|
+
dr = Http.get("#{BASE_URL}/api/email-box/#{URI.encode_www_form_component(uuid)}/email/#{URI.encode_www_form_component(message_id)}",
|
|
62
|
+
headers: HEADERS)
|
|
63
|
+
if dr.ok?
|
|
64
|
+
detail = dr.json
|
|
65
|
+
raw = detail if detail.is_a?(Hash)
|
|
66
|
+
end
|
|
67
|
+
rescue StandardError
|
|
68
|
+
# 忽略
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
Normalize.normalize_email(flatten(raw, address), address)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module TempmailSdk
|
|
6
|
+
module Providers
|
|
7
|
+
# TempGmailer 渠道 — https://tempgmailer.com
|
|
8
|
+
# Laravel + CSRF,POST /get-gmail 创建,POST /get-inbox 获取邮件
|
|
9
|
+
module Tempgmailer
|
|
10
|
+
CHANNEL = "tempgmailer"
|
|
11
|
+
BASE_URL = "https://tempgmailer.com"
|
|
12
|
+
|
|
13
|
+
# 从 HTML 中提取 CSRF token
|
|
14
|
+
CSRF_RE = /<meta\s+name="csrf-token"\s+content="([^"]+)"/
|
|
15
|
+
|
|
16
|
+
HEADERS = {
|
|
17
|
+
"User-Agent" => "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",
|
|
18
|
+
"Accept" => "application/json, text/plain, */*",
|
|
19
|
+
"Accept-Language" => "zh-CN,zh;q=0.9,en;q=0.8",
|
|
20
|
+
"Referer" => "#{BASE_URL}/",
|
|
21
|
+
"X-Requested-With" => "XMLHttpRequest",
|
|
22
|
+
"X-TempGmailer-Auth" => "frontend",
|
|
23
|
+
"Content-Type" => "application/json"
|
|
24
|
+
}.freeze
|
|
25
|
+
|
|
26
|
+
module_function
|
|
27
|
+
|
|
28
|
+
def extract_cookies(resp)
|
|
29
|
+
cookies = {}
|
|
30
|
+
resp.set_cookies.each do |line|
|
|
31
|
+
pair = line.split(";").first.to_s.strip
|
|
32
|
+
k, v = pair.split("=", 2)
|
|
33
|
+
cookies[k] = v if k && v
|
|
34
|
+
end
|
|
35
|
+
cookies
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def init_session
|
|
39
|
+
resp = Http.get(BASE_URL, headers: {
|
|
40
|
+
"User-Agent" => HEADERS["User-Agent"],
|
|
41
|
+
"Accept" => "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
|
|
42
|
+
}, timeout: 15)
|
|
43
|
+
resp.raise_for_status
|
|
44
|
+
|
|
45
|
+
m = resp.body.match(CSRF_RE)
|
|
46
|
+
raise "tempgmailer: 无法提取 CSRF token" unless m
|
|
47
|
+
|
|
48
|
+
csrf_token = m[1]
|
|
49
|
+
cookies = extract_cookies(resp)
|
|
50
|
+
cookie_str = cookies.map { |k, v| "#{k}=#{v}" }.join("; ")
|
|
51
|
+
raise "tempgmailer: 未获取到 session cookie" if cookie_str.empty?
|
|
52
|
+
|
|
53
|
+
[csrf_token, cookie_str]
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# 创建临时邮箱
|
|
57
|
+
# @return [EmailInfo]
|
|
58
|
+
def generate_email
|
|
59
|
+
csrf_token, cookie_str = init_session
|
|
60
|
+
|
|
61
|
+
headers = HEADERS.merge(
|
|
62
|
+
"X-CSRF-TOKEN" => csrf_token,
|
|
63
|
+
"Cookie" => cookie_str
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
resp = Http.post("#{BASE_URL}/get-gmail", headers: headers,
|
|
67
|
+
json: { "refresh" => true, "adblock" => 0 })
|
|
68
|
+
resp.raise_for_status
|
|
69
|
+
data = resp.json
|
|
70
|
+
raise "tempgmailer: 创建邮箱失败: #{data['message'] || '未知错误'}" unless data["success"]
|
|
71
|
+
|
|
72
|
+
email = ((data["data"] || {})["email"] || "").strip
|
|
73
|
+
raise "tempgmailer: 创建邮箱失败,未返回邮箱地址" if email.empty?
|
|
74
|
+
|
|
75
|
+
token_payload = JSON.generate({ "csrfToken" => csrf_token, "cookies" => cookie_str })
|
|
76
|
+
EmailInfo.new(channel: CHANNEL, email: email, token: token_payload)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# 获取邮件列表
|
|
80
|
+
# @param token [String] JSON 格式认证信息
|
|
81
|
+
# @param email [String]
|
|
82
|
+
# @return [Array<Email>]
|
|
83
|
+
def get_emails(token, email)
|
|
84
|
+
session = JSON.parse(token)
|
|
85
|
+
csrf_token = session["csrfToken"]
|
|
86
|
+
cookie_str = session["cookies"]
|
|
87
|
+
|
|
88
|
+
headers = HEADERS.merge(
|
|
89
|
+
"X-CSRF-TOKEN" => csrf_token,
|
|
90
|
+
"Cookie" => cookie_str
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
resp = Http.post("#{BASE_URL}/get-inbox", headers: headers,
|
|
94
|
+
json: { "email" => email, "adblock" => 0 })
|
|
95
|
+
resp.raise_for_status
|
|
96
|
+
data = resp.json
|
|
97
|
+
return [] unless data["success"]
|
|
98
|
+
|
|
99
|
+
messages = (data["data"] || {})["messages"] || []
|
|
100
|
+
return [] unless messages.is_a?(Array)
|
|
101
|
+
|
|
102
|
+
messages.filter_map do |msg|
|
|
103
|
+
next unless msg.is_a?(Hash)
|
|
104
|
+
|
|
105
|
+
from_field = msg["from"]
|
|
106
|
+
from_field = from_field["address"] if from_field.is_a?(Hash)
|
|
107
|
+
from_field = from_field.to_s
|
|
108
|
+
|
|
109
|
+
html_content = msg["body"] || msg["intro"] || ""
|
|
110
|
+
text_content = msg["intro"] || ""
|
|
111
|
+
|
|
112
|
+
raw = {
|
|
113
|
+
"id" => msg["id"] || "",
|
|
114
|
+
"from" => from_field,
|
|
115
|
+
"to" => email,
|
|
116
|
+
"subject" => msg["subject"] || "",
|
|
117
|
+
"text" => text_content,
|
|
118
|
+
"html" => html_content,
|
|
119
|
+
"date" => msg["createdAt"] || ""
|
|
120
|
+
}
|
|
121
|
+
Normalize.normalize_email(raw, email)
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "uri"
|
|
4
|
+
|
|
5
|
+
module TempmailSdk
|
|
6
|
+
module Providers
|
|
7
|
+
# tempgo-email 渠道 — https://tempgo.email
|
|
8
|
+
# POST /api/generate 创建邮箱,GET /api/inbox 获取邮件
|
|
9
|
+
module TempgoEmail
|
|
10
|
+
CHANNEL = "tempgo-email"
|
|
11
|
+
BASE_URL = "https://tempgo.email"
|
|
12
|
+
|
|
13
|
+
HEADERS = {
|
|
14
|
+
"User-Agent" => "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36",
|
|
15
|
+
"Accept" => "application/json"
|
|
16
|
+
}.freeze
|
|
17
|
+
|
|
18
|
+
module_function
|
|
19
|
+
|
|
20
|
+
# 创建临时邮箱
|
|
21
|
+
# @return [EmailInfo]
|
|
22
|
+
def generate_email
|
|
23
|
+
resp = Http.post("#{BASE_URL}/api/generate", headers: HEADERS, timeout: 15)
|
|
24
|
+
resp.raise_for_status
|
|
25
|
+
body = resp.json
|
|
26
|
+
raise "tempgo-email: 响应格式无效" unless body.is_a?(Hash)
|
|
27
|
+
|
|
28
|
+
address = body["email"] || ""
|
|
29
|
+
mailbox_id = body["mailbox_id"] || ""
|
|
30
|
+
raise "tempgo-email: 缺少邮箱地址" if address.empty?
|
|
31
|
+
raise "tempgo-email: 缺少 mailbox_id" if mailbox_id.empty?
|
|
32
|
+
|
|
33
|
+
EmailInfo.new(channel: CHANNEL, email: address, token: mailbox_id)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# 获取邮件列表
|
|
37
|
+
# @param token [String] mailbox_id
|
|
38
|
+
# @param email [String]
|
|
39
|
+
# @return [Array<Email>]
|
|
40
|
+
def get_emails(token, email)
|
|
41
|
+
raise "tempgo-email: token 不能为空" if token.nil? || token.empty?
|
|
42
|
+
|
|
43
|
+
address = (email || "").strip
|
|
44
|
+
raise "tempgo-email: 缺少邮箱地址" if address.empty?
|
|
45
|
+
|
|
46
|
+
resp = Http.get("#{BASE_URL}/api/inbox?email=#{URI.encode_www_form_component(address)}&mailbox_id=#{URI.encode_www_form_component(token)}",
|
|
47
|
+
headers: HEADERS, timeout: 15)
|
|
48
|
+
return [] if resp.status_code == 404
|
|
49
|
+
resp.raise_for_status
|
|
50
|
+
|
|
51
|
+
body = resp.json
|
|
52
|
+
return [] unless body.is_a?(Hash)
|
|
53
|
+
|
|
54
|
+
messages = body["messages"]
|
|
55
|
+
return [] unless messages.is_a?(Array) && !messages.empty?
|
|
56
|
+
|
|
57
|
+
messages.filter_map do |msg|
|
|
58
|
+
next unless msg.is_a?(Hash)
|
|
59
|
+
|
|
60
|
+
raw = {
|
|
61
|
+
"id" => msg["id"] || "",
|
|
62
|
+
"from" => msg["from"] || msg["sender"] || "",
|
|
63
|
+
"to" => address,
|
|
64
|
+
"subject" => msg["subject"] || "",
|
|
65
|
+
"text" => msg["text"] || msg["body_plain"] || "",
|
|
66
|
+
"html" => msg["html"] || msg["body_html"] || "",
|
|
67
|
+
"date" => (msg["date"] || msg["received_at"] || "").to_s
|
|
68
|
+
}
|
|
69
|
+
Normalize.normalize_email(raw, address)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module TempmailSdk
|
|
4
|
+
module Providers
|
|
5
|
+
# TempInbox 渠道实现
|
|
6
|
+
# API: https://endpoint.tempinbox.xyz
|
|
7
|
+
module Tempinbox
|
|
8
|
+
CHANNEL = "tempinbox"
|
|
9
|
+
BASE = "https://endpoint.tempinbox.xyz"
|
|
10
|
+
|
|
11
|
+
# 支持的域名列表
|
|
12
|
+
DOMAINS = %w[tempinbox.xyz thepiratebay.cloud cryptoblad.nl].freeze
|
|
13
|
+
|
|
14
|
+
HEADERS = {
|
|
15
|
+
"Accept" => "application/json, text/plain, */*",
|
|
16
|
+
"Cache-Control" => "no-cache",
|
|
17
|
+
"DNT" => "1",
|
|
18
|
+
"Referer" => "https://www.tempinbox.xyz/",
|
|
19
|
+
"User-Agent" => "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"
|
|
20
|
+
}.freeze
|
|
21
|
+
|
|
22
|
+
module_function
|
|
23
|
+
|
|
24
|
+
# 生成随机用户名
|
|
25
|
+
def random_user(length = 10)
|
|
26
|
+
chars = ("a".."z").to_a + ("0".."9").to_a
|
|
27
|
+
Array.new(length) { chars.sample }.join
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# 创建临时邮箱
|
|
31
|
+
# @param domain [String, nil]
|
|
32
|
+
# @return [EmailInfo]
|
|
33
|
+
def generate_email(domain = nil)
|
|
34
|
+
if domain && !domain.to_s.strip.empty?
|
|
35
|
+
d = domain.strip.downcase
|
|
36
|
+
raise "tempinbox: 域名不可用: #{d}" unless DOMAINS.include?(d)
|
|
37
|
+
|
|
38
|
+
user = random_user
|
|
39
|
+
addr = "#{user}@#{d}"
|
|
40
|
+
Http.get("#{BASE}/email/#{addr}", headers: HEADERS, timeout: 15).raise_for_status
|
|
41
|
+
else
|
|
42
|
+
resp = Http.get("#{BASE}/email/Random", headers: HEADERS, timeout: 15)
|
|
43
|
+
resp.raise_for_status
|
|
44
|
+
addr = resp.body.strip.delete('"')
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
raise "tempinbox: 无效的邮箱地址" if addr.to_s.empty? || !addr.include?("@")
|
|
48
|
+
|
|
49
|
+
EmailInfo.new(channel: CHANNEL, email: addr)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# 获取邮件列表
|
|
53
|
+
# @param email [String]
|
|
54
|
+
# @return [Array<Email>]
|
|
55
|
+
def get_emails(email)
|
|
56
|
+
addr = email.to_s.strip
|
|
57
|
+
raise "tempinbox: 邮箱地址为空" if addr.empty?
|
|
58
|
+
|
|
59
|
+
resp = Http.get("#{BASE}/messages/#{addr}", headers: HEADERS, timeout: 15)
|
|
60
|
+
resp.raise_for_status
|
|
61
|
+
data = resp.json
|
|
62
|
+
return [] unless data.is_a?(Array)
|
|
63
|
+
|
|
64
|
+
data.select { |raw| raw.is_a?(Hash) }.map do |raw|
|
|
65
|
+
Normalize.normalize_email(raw, addr)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module TempmailSdk
|
|
4
|
+
module Providers
|
|
5
|
+
# tempmail.ing 渠道实现
|
|
6
|
+
# API: https://api.tempmail.ing/api
|
|
7
|
+
module Tempmail
|
|
8
|
+
CHANNEL = "tempmail"
|
|
9
|
+
BASE_URL = "https://api.tempmail.ing/api"
|
|
10
|
+
|
|
11
|
+
DEFAULT_HEADERS = {
|
|
12
|
+
"User-Agent" => "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36",
|
|
13
|
+
"Content-Type" => "application/json",
|
|
14
|
+
"Referer" => "https://tempmail.ing/",
|
|
15
|
+
"DNT" => "1"
|
|
16
|
+
}.freeze
|
|
17
|
+
|
|
18
|
+
module_function
|
|
19
|
+
|
|
20
|
+
# 创建临时邮箱,支持自定义有效期(分钟)
|
|
21
|
+
# @param duration [Integer]
|
|
22
|
+
# @return [EmailInfo]
|
|
23
|
+
def generate_email(duration = 30)
|
|
24
|
+
resp = Http.post("#{BASE_URL}/generate", headers: DEFAULT_HEADERS, json: { "duration" => duration })
|
|
25
|
+
resp.raise_for_status
|
|
26
|
+
data = resp.json
|
|
27
|
+
raise "Failed to generate email" unless data["success"]
|
|
28
|
+
|
|
29
|
+
info = data["email"]
|
|
30
|
+
EmailInfo.new(
|
|
31
|
+
channel: CHANNEL,
|
|
32
|
+
email: info["address"],
|
|
33
|
+
expires_at: info["expiresAt"],
|
|
34
|
+
created_at: info["createdAt"]
|
|
35
|
+
)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# 获取邮件列表
|
|
39
|
+
# @param email [String]
|
|
40
|
+
# @return [Array<Email>]
|
|
41
|
+
def get_emails(email)
|
|
42
|
+
resp = Http.get("#{BASE_URL}/emails/#{URI.encode_www_form_component(email)}", headers: DEFAULT_HEADERS)
|
|
43
|
+
resp.raise_for_status
|
|
44
|
+
data = resp.json
|
|
45
|
+
raise "Failed to get emails" unless data["success"]
|
|
46
|
+
|
|
47
|
+
(data["emails"] || []).map { |raw| Normalize.normalize_email(flatten(raw, email), email) }
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# 将原始格式扁平化:content -> html, from_address -> from, received_at -> date
|
|
51
|
+
def flatten(raw, recipient)
|
|
52
|
+
{
|
|
53
|
+
"id" => raw["id"] || "",
|
|
54
|
+
"from" => raw["from_address"] || raw["from"] || "",
|
|
55
|
+
"to" => recipient,
|
|
56
|
+
"subject" => raw["subject"] || "",
|
|
57
|
+
"text" => raw["text"] || "",
|
|
58
|
+
"html" => raw["content"] || raw["html"] || "",
|
|
59
|
+
"date" => raw["received_at"] || raw["date"] || "",
|
|
60
|
+
"is_read" => raw["is_read"] == 1 || raw["is_read"] == true,
|
|
61
|
+
"attachments" => raw["attachments"] || []
|
|
62
|
+
}
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "time"
|
|
4
|
+
|
|
5
|
+
module TempmailSdk
|
|
6
|
+
module Providers
|
|
7
|
+
# Tempmail365 渠道实现
|
|
8
|
+
# API: https://tempmail365.cn/tempemail.php
|
|
9
|
+
module Tempmail365
|
|
10
|
+
CHANNEL = "tempmail365"
|
|
11
|
+
BASE = "https://tempmail365.cn/tempemail.php"
|
|
12
|
+
|
|
13
|
+
# 后备域名列表
|
|
14
|
+
FALLBACK_DOMAINS = %w[fengyou.cc shop345.com nutemail.com qvrf.cn].freeze
|
|
15
|
+
|
|
16
|
+
HEADERS = {
|
|
17
|
+
"Accept" => "application/json, text/plain, */*",
|
|
18
|
+
"Cache-Control" => "no-cache",
|
|
19
|
+
"DNT" => "1",
|
|
20
|
+
"Referer" => "https://tempmail365.cn/",
|
|
21
|
+
"User-Agent" => "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"
|
|
22
|
+
}.freeze
|
|
23
|
+
|
|
24
|
+
module_function
|
|
25
|
+
|
|
26
|
+
# 从 API 获取可用域名列表
|
|
27
|
+
def fetch_domains
|
|
28
|
+
resp = Http.get("#{BASE}?action=get_config", headers: HEADERS, timeout: 15)
|
|
29
|
+
resp.raise_for_status
|
|
30
|
+
data = resp.json
|
|
31
|
+
return FALLBACK_DOMAINS.dup unless data.is_a?(Hash)
|
|
32
|
+
|
|
33
|
+
raw = data["domains"]
|
|
34
|
+
return FALLBACK_DOMAINS.dup unless raw.is_a?(Array) && !raw.empty?
|
|
35
|
+
|
|
36
|
+
out = raw.select { |d| d.is_a?(String) && !d.strip.empty? }.map(&:strip)
|
|
37
|
+
out.empty? ? FALLBACK_DOMAINS.dup : out
|
|
38
|
+
rescue StandardError
|
|
39
|
+
FALLBACK_DOMAINS.dup
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# 生成随机用户名
|
|
43
|
+
def random_username(length = 8)
|
|
44
|
+
chars = ("a".."z").to_a + ("0".."9").to_a
|
|
45
|
+
Array.new(length) { chars.sample }.join
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# 从 HTML 邮件内容中提取发件人
|
|
49
|
+
def extract_sender(content)
|
|
50
|
+
m = content.match(/(?:发件人|From)\s*[::]\s*(.+?)(?:<br|<\/|<p|\n|\r)/i)
|
|
51
|
+
m ? m[1].strip : ""
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# 从 HTML 邮件内容中提取主题
|
|
55
|
+
def extract_subject(content)
|
|
56
|
+
m = content.match(/(?:主题|Subject)\s*[::]\s*(.+?)(?:<br|<\/|<p|\n|\r)/i)
|
|
57
|
+
m ? m[1].strip : ""
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# 创建临时邮箱
|
|
61
|
+
# @param domain [String, nil]
|
|
62
|
+
# @return [EmailInfo]
|
|
63
|
+
def generate_email(domain = nil)
|
|
64
|
+
domains = fetch_domains
|
|
65
|
+
raise "tempmail365: 无可用域名" if domains.empty?
|
|
66
|
+
|
|
67
|
+
if domain && !domain.to_s.strip.empty?
|
|
68
|
+
d = domain.strip.downcase
|
|
69
|
+
matched = domains.select { |x| x.downcase == d }
|
|
70
|
+
raise "tempmail365: 域名不可用: #{d}" if matched.empty?
|
|
71
|
+
|
|
72
|
+
selected = matched.first
|
|
73
|
+
else
|
|
74
|
+
selected = domains.sample
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
username = random_username
|
|
78
|
+
addr = "#{username}@#{selected}"
|
|
79
|
+
|
|
80
|
+
url = "#{BASE}?action=create_email&email=#{URI.encode_www_form_component(addr)}&domain=#{URI.encode_www_form_component(selected)}"
|
|
81
|
+
resp = Http.get(url, headers: HEADERS, timeout: 15)
|
|
82
|
+
resp.raise_for_status
|
|
83
|
+
data = resp.json
|
|
84
|
+
raise "tempmail365: 创建邮箱失败" unless data.is_a?(Hash) && data["success"]
|
|
85
|
+
|
|
86
|
+
EmailInfo.new(channel: CHANNEL, email: addr)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# 获取邮件列表
|
|
90
|
+
# @param email [String]
|
|
91
|
+
# @return [Array<Email>]
|
|
92
|
+
def get_emails(email)
|
|
93
|
+
addr = email.to_s.strip
|
|
94
|
+
raise "tempmail365: 邮箱地址为空" if addr.empty?
|
|
95
|
+
|
|
96
|
+
resp = Http.get("#{BASE}?action=fetch_mail&email=#{URI.encode_www_form_component(addr)}",
|
|
97
|
+
headers: HEADERS, timeout: 15)
|
|
98
|
+
resp.raise_for_status
|
|
99
|
+
data = resp.json
|
|
100
|
+
return [] unless data.is_a?(Hash)
|
|
101
|
+
|
|
102
|
+
content = data["content"]
|
|
103
|
+
return [] if content.nil? || !content.is_a?(String) || content.strip.empty?
|
|
104
|
+
return [] if content.strip == "无邮件"
|
|
105
|
+
|
|
106
|
+
sender = extract_sender(content)
|
|
107
|
+
subject = extract_subject(content)
|
|
108
|
+
|
|
109
|
+
raw = {
|
|
110
|
+
"from" => sender,
|
|
111
|
+
"subject" => subject,
|
|
112
|
+
"body" => content,
|
|
113
|
+
"date" => Time.now.utc.iso8601
|
|
114
|
+
}
|
|
115
|
+
[Normalize.normalize_email(raw, addr)]
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "uri"
|
|
5
|
+
|
|
6
|
+
module TempmailSdk
|
|
7
|
+
module Providers
|
|
8
|
+
# TempMail Fish 渠道 — https://tempmail.fish
|
|
9
|
+
# POST /emails/new-email 创建邮箱,GET /emails/emails 获取邮件
|
|
10
|
+
module TempmailFish
|
|
11
|
+
CHANNEL = "tempmail-fish"
|
|
12
|
+
API_BASE = "https://api.tempmail.fish"
|
|
13
|
+
HEADERS = {
|
|
14
|
+
"Accept" => "application/json",
|
|
15
|
+
"User-Agent" => "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36"
|
|
16
|
+
}.freeze
|
|
17
|
+
|
|
18
|
+
module_function
|
|
19
|
+
|
|
20
|
+
# 将毫秒时间戳转换为 ISO8601 字符串
|
|
21
|
+
def to_iso(timestamp)
|
|
22
|
+
return "" if timestamp.nil?
|
|
23
|
+
|
|
24
|
+
millis = Float(timestamp)
|
|
25
|
+
Time.at(millis / 1000.0).utc.iso8601
|
|
26
|
+
rescue ArgumentError, TypeError
|
|
27
|
+
""
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# @return [EmailInfo]
|
|
31
|
+
def generate_email
|
|
32
|
+
resp = Http.post("#{API_BASE}/emails/new-email",
|
|
33
|
+
headers: HEADERS.merge("Content-Type" => "application/json"))
|
|
34
|
+
resp.raise_for_status
|
|
35
|
+
data = resp.json
|
|
36
|
+
raise "tempmail-fish: 创建邮箱响应无效" unless data.is_a?(Hash)
|
|
37
|
+
|
|
38
|
+
email = (data["email"] || "").strip
|
|
39
|
+
auth_key = (data["authKey"] || "").strip
|
|
40
|
+
raise "tempmail-fish: 创建邮箱响应无效" if email.empty? || !email.include?("@") || auth_key.empty?
|
|
41
|
+
|
|
42
|
+
token = JSON.generate({ "email" => email, "authKey" => auth_key })
|
|
43
|
+
EmailInfo.new(channel: CHANNEL, email: email, token: token)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# @param token [String] JSON 字符串
|
|
47
|
+
# @param email [String]
|
|
48
|
+
# @return [Array<Email>]
|
|
49
|
+
def get_emails(token, email)
|
|
50
|
+
raise "tempmail-fish: token 不能为空" if token.nil? || token.empty?
|
|
51
|
+
|
|
52
|
+
parsed = JSON.parse(token)
|
|
53
|
+
raise "tempmail-fish: token 格式无效" unless parsed.is_a?(Hash)
|
|
54
|
+
|
|
55
|
+
address = (parsed["email"] || email || "").strip
|
|
56
|
+
auth_key = (parsed["authKey"] || "").strip
|
|
57
|
+
raise "tempmail-fish: token 缺少 email 或 authKey" if address.empty? || auth_key.empty?
|
|
58
|
+
|
|
59
|
+
resp = Http.get("#{API_BASE}/emails/emails?emailAddress=#{URI.encode_www_form_component(address)}",
|
|
60
|
+
headers: HEADERS.merge("Authorization" => auth_key))
|
|
61
|
+
resp.raise_for_status
|
|
62
|
+
data = resp.json
|
|
63
|
+
|
|
64
|
+
rows = if data.is_a?(Array)
|
|
65
|
+
data
|
|
66
|
+
elsif data.is_a?(Hash) && data["emails"].is_a?(Array)
|
|
67
|
+
data["emails"]
|
|
68
|
+
else
|
|
69
|
+
[]
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
rows.filter_map do |row|
|
|
73
|
+
next unless row.is_a?(Hash)
|
|
74
|
+
|
|
75
|
+
raw = {
|
|
76
|
+
"from" => (row["from"] || "").to_s,
|
|
77
|
+
"to" => (row["to"] || "").to_s.empty? ? address : row["to"].to_s,
|
|
78
|
+
"subject" => (row["subject"] || "").to_s,
|
|
79
|
+
"text" => (row["textBody"] || "").to_s,
|
|
80
|
+
"html" => (row["htmlBody"] || "").to_s,
|
|
81
|
+
"date" => to_iso(row["timestamp"]),
|
|
82
|
+
"attachments" => row["attachments"] || []
|
|
83
|
+
}
|
|
84
|
+
Normalize.normalize_email(raw, address)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|