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,99 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "securerandom"
|
|
4
|
+
|
|
5
|
+
module TempmailSdk
|
|
6
|
+
module Providers
|
|
7
|
+
# mail.tm 渠道实现
|
|
8
|
+
# API: https://api.mail.tm
|
|
9
|
+
# 流程: 获取域名 -> 生成随机邮箱/密码 -> 创建账号 -> 获取 Bearer Token
|
|
10
|
+
module MailTm
|
|
11
|
+
CHANNEL = "mail-tm"
|
|
12
|
+
BASE_URL = "https://api.mail.tm"
|
|
13
|
+
DEFAULT_HEADERS = { "Content-Type" => "application/json", "Accept" => "application/json" }.freeze
|
|
14
|
+
|
|
15
|
+
module_function
|
|
16
|
+
|
|
17
|
+
def random_string(length)
|
|
18
|
+
chars = ("a".."z").to_a + ("0".."9").to_a
|
|
19
|
+
Array.new(length) { chars.sample }.join
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# 获取可用域名列表,兼容 Hydra 格式和纯数组
|
|
23
|
+
def fetch_domains
|
|
24
|
+
resp = Http.get("#{BASE_URL}/domains", headers: DEFAULT_HEADERS)
|
|
25
|
+
resp.raise_for_status
|
|
26
|
+
data = resp.json
|
|
27
|
+
members = data.is_a?(Array) ? data : (data["hydra:member"] || [])
|
|
28
|
+
members.select { |d| d["isActive"] }.map { |d| d["domain"] }
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def create_account(address, password)
|
|
32
|
+
resp = Http.post("#{BASE_URL}/accounts",
|
|
33
|
+
headers: DEFAULT_HEADERS.merge("Content-Type" => "application/ld+json"),
|
|
34
|
+
json: { "address" => address, "password" => password })
|
|
35
|
+
resp.raise_for_status
|
|
36
|
+
resp.json
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def fetch_token(address, password)
|
|
40
|
+
resp = Http.post("#{BASE_URL}/token", headers: DEFAULT_HEADERS,
|
|
41
|
+
json: { "address" => address, "password" => password })
|
|
42
|
+
resp.raise_for_status
|
|
43
|
+
resp.json["token"]
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# @return [EmailInfo]
|
|
47
|
+
def generate_email
|
|
48
|
+
domains = fetch_domains
|
|
49
|
+
raise "No available domains" if domains.empty?
|
|
50
|
+
|
|
51
|
+
address = "#{random_string(12)}@#{domains.sample}"
|
|
52
|
+
password = random_string(16)
|
|
53
|
+
account = create_account(address, password)
|
|
54
|
+
token = fetch_token(address, password)
|
|
55
|
+
EmailInfo.new(channel: CHANNEL, email: address, token: token, created_at: account["createdAt"])
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# @param email [String]
|
|
59
|
+
# @param token [String]
|
|
60
|
+
# @return [Array<Email>]
|
|
61
|
+
def get_emails(email, token)
|
|
62
|
+
resp = Http.get("#{BASE_URL}/messages",
|
|
63
|
+
headers: DEFAULT_HEADERS.merge("Authorization" => "Bearer #{token}"))
|
|
64
|
+
resp.raise_for_status
|
|
65
|
+
data = resp.json
|
|
66
|
+
messages = data.is_a?(Array) ? data : (data["hydra:member"] || [])
|
|
67
|
+
return [] if messages.empty?
|
|
68
|
+
|
|
69
|
+
messages.map do |msg|
|
|
70
|
+
flat = begin
|
|
71
|
+
dr = Http.get("#{BASE_URL}/messages/#{msg['id']}",
|
|
72
|
+
headers: DEFAULT_HEADERS.merge("Authorization" => "Bearer #{token}"))
|
|
73
|
+
flatten(dr.ok? ? dr.json : msg, email)
|
|
74
|
+
rescue StandardError
|
|
75
|
+
flatten(msg, email)
|
|
76
|
+
end
|
|
77
|
+
Normalize.normalize_email(flat, email)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# 将 mail.tm 消息格式扁平化
|
|
82
|
+
def flatten(msg, recipient)
|
|
83
|
+
from_addr = msg["from"].is_a?(Hash) ? msg["from"]["address"].to_s : msg["from"].to_s
|
|
84
|
+
to_addr = recipient
|
|
85
|
+
if msg["to"].is_a?(Array) && msg["to"][0].is_a?(Hash)
|
|
86
|
+
to_addr = msg["to"][0]["address"] || recipient
|
|
87
|
+
end
|
|
88
|
+
html = msg["html"]
|
|
89
|
+
html = html.join if html.is_a?(Array)
|
|
90
|
+
{
|
|
91
|
+
"id" => msg["id"] || "", "from" => from_addr, "to" => to_addr,
|
|
92
|
+
"subject" => msg["subject"] || "", "text" => msg["text"] || "",
|
|
93
|
+
"html" => html || "", "createdAt" => msg["createdAt"] || "",
|
|
94
|
+
"seen" => msg["seen"] || false
|
|
95
|
+
}
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "securerandom"
|
|
4
|
+
|
|
5
|
+
module TempmailSdk
|
|
6
|
+
module Providers
|
|
7
|
+
# MailCatch 渠道 — https://mailcatch.com
|
|
8
|
+
# 公开收件箱,无需认证。GET /api/list/{inbox} + GET /api/data/{inbox}/{id}
|
|
9
|
+
module Mailcatch
|
|
10
|
+
CHANNEL = "mailcatch"
|
|
11
|
+
BASE_URL = "https://mailcatch.com"
|
|
12
|
+
DOMAIN = "mailcatch.com"
|
|
13
|
+
|
|
14
|
+
# 匹配邮件列表 HTML 中的邮件项
|
|
15
|
+
EMAIL_ITEM_RE = /class="email-item"\s+data-email-id="([^"]*)"\s+data-subject="([^"]*)"\s+data-timestamp="([^"]*)"\s+data-sender="([^"]*)"/i
|
|
16
|
+
|
|
17
|
+
HEADERS = {
|
|
18
|
+
"Accept" => "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
|
|
19
|
+
"User-Agent" => "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " \
|
|
20
|
+
"(KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36 Edg/146.0.0.0"
|
|
21
|
+
}.freeze
|
|
22
|
+
|
|
23
|
+
module_function
|
|
24
|
+
|
|
25
|
+
# 生成随机用户名
|
|
26
|
+
def random_local
|
|
27
|
+
chars = ("a".."z").to_a + ("0".."9").to_a
|
|
28
|
+
"sdk" + Array.new(16) { chars.sample }.join
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# 提取邮箱地址的 @ 前部分
|
|
32
|
+
def local_part(email)
|
|
33
|
+
(email || "").strip.split("@").first.to_s.strip
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# 创建 mailcatch 临时邮箱
|
|
37
|
+
# @return [EmailInfo]
|
|
38
|
+
def generate_email
|
|
39
|
+
local = random_local
|
|
40
|
+
email = "#{local}@#{DOMAIN}"
|
|
41
|
+
EmailInfo.new(channel: CHANNEL, email: email, token: local)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# 获取邮件列表
|
|
45
|
+
# @param email [String]
|
|
46
|
+
# @param token [String]
|
|
47
|
+
# @return [Array<Email>]
|
|
48
|
+
def get_emails(email, token)
|
|
49
|
+
raise "mailcatch: token 不能为空" if token.nil? || token.to_s.empty?
|
|
50
|
+
|
|
51
|
+
addr = (email || "").strip
|
|
52
|
+
raise "mailcatch: 邮箱地址不能为空" if addr.empty?
|
|
53
|
+
|
|
54
|
+
inbox = token.strip
|
|
55
|
+
inbox = local_part(addr) if inbox.empty?
|
|
56
|
+
raise "mailcatch: 无法确定收件箱名称" if inbox.empty?
|
|
57
|
+
|
|
58
|
+
# 获取邮件列表 HTML
|
|
59
|
+
resp = Http.get("#{BASE_URL}/api/list/#{inbox}", headers: HEADERS, timeout: 15)
|
|
60
|
+
resp.raise_for_status
|
|
61
|
+
|
|
62
|
+
items = resp.body.scan(EMAIL_ITEM_RE)
|
|
63
|
+
return [] if items.empty?
|
|
64
|
+
|
|
65
|
+
items.filter_map do |email_id, subject, timestamp, sender|
|
|
66
|
+
email_id = email_id.strip
|
|
67
|
+
next if email_id.empty?
|
|
68
|
+
|
|
69
|
+
# 获取邮件正文 HTML
|
|
70
|
+
body_html = ""
|
|
71
|
+
begin
|
|
72
|
+
detail_resp = Http.get("#{BASE_URL}/api/data/#{inbox}/#{email_id}", headers: HEADERS, timeout: 15)
|
|
73
|
+
body_html = detail_resp.body.strip if detail_resp.ok?
|
|
74
|
+
rescue StandardError
|
|
75
|
+
# 忽略正文获取失败
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
raw = {
|
|
79
|
+
"id" => email_id,
|
|
80
|
+
"from" => sender.strip,
|
|
81
|
+
"to" => addr,
|
|
82
|
+
"subject" => subject.strip,
|
|
83
|
+
"html" => body_html,
|
|
84
|
+
"date" => timestamp.strip
|
|
85
|
+
}
|
|
86
|
+
Normalize.normalize_email(raw, addr)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module TempmailSdk
|
|
4
|
+
module Providers
|
|
5
|
+
# Maildrop — https://maildrop.cx
|
|
6
|
+
# GET /api/suffixes.php、GET /api/emails.php
|
|
7
|
+
module Maildrop
|
|
8
|
+
CHANNEL = "maildrop"
|
|
9
|
+
BASE = "https://maildrop.cx"
|
|
10
|
+
EXCLUDED_SUFFIX = "transformer.edu.kg"
|
|
11
|
+
HEADERS = {
|
|
12
|
+
"Accept" => "application/json, text/plain, */*",
|
|
13
|
+
"Referer" => "https://maildrop.cx/zh-cn/app",
|
|
14
|
+
"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",
|
|
15
|
+
"x-requested-with" => "XMLHttpRequest"
|
|
16
|
+
}.freeze
|
|
17
|
+
|
|
18
|
+
module_function
|
|
19
|
+
|
|
20
|
+
def random_local(length = 10)
|
|
21
|
+
chars = ("a".."z").to_a + ("0".."9").to_a
|
|
22
|
+
Array.new(length) { chars.sample }.join
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def fetch_suffixes
|
|
26
|
+
r = Http.get("#{BASE}/api/suffixes.php", headers: HEADERS)
|
|
27
|
+
r.raise_for_status
|
|
28
|
+
data = r.json
|
|
29
|
+
raise "maildrop: invalid suffixes response" unless data.is_a?(Array)
|
|
30
|
+
|
|
31
|
+
ex = EXCLUDED_SUFFIX.downcase
|
|
32
|
+
out = data.select { |x| x.is_a?(String) }.map(&:strip).reject { |t| t.empty? || t.downcase == ex }
|
|
33
|
+
raise "maildrop: no domains available" if out.empty?
|
|
34
|
+
|
|
35
|
+
out
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def pick_suffix(suffixes, preferred)
|
|
39
|
+
if preferred && !preferred.to_s.strip.empty?
|
|
40
|
+
p = preferred.to_s.strip.downcase
|
|
41
|
+
found = suffixes.find { |d| d.downcase == p }
|
|
42
|
+
raise "maildrop: domain not available: #{p}" unless found
|
|
43
|
+
|
|
44
|
+
return found
|
|
45
|
+
end
|
|
46
|
+
suffixes.sample
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def cx_date_to_iso(str)
|
|
50
|
+
s = str.to_s.strip
|
|
51
|
+
return "#{s[0, 10]}T#{s[11..]}Z" if s.length == 19 && s[10] == " "
|
|
52
|
+
|
|
53
|
+
s
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# @param domain [String, nil]
|
|
57
|
+
# @return [EmailInfo]
|
|
58
|
+
def generate_email(domain = nil)
|
|
59
|
+
suffixes = fetch_suffixes
|
|
60
|
+
dom = pick_suffix(suffixes, domain)
|
|
61
|
+
email = "#{random_local}@#{dom}"
|
|
62
|
+
EmailInfo.new(channel: CHANNEL, email: email, token: email)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# @param email [String]
|
|
66
|
+
# @param token [String, nil]
|
|
67
|
+
# @return [Array<Email>]
|
|
68
|
+
def get_emails(email, token = nil)
|
|
69
|
+
addr = email.to_s.strip
|
|
70
|
+
addr = token.to_s.strip if addr.empty?
|
|
71
|
+
raise "maildrop: empty address" if addr.empty?
|
|
72
|
+
|
|
73
|
+
qs = URI.encode_www_form(addr: addr, page: "1", limit: "20")
|
|
74
|
+
r = Http.get("#{BASE}/api/emails.php?#{qs}", headers: HEADERS)
|
|
75
|
+
r.raise_for_status
|
|
76
|
+
data = r.body.empty? ? {} : r.json
|
|
77
|
+
rows = data["emails"] || []
|
|
78
|
+
return [] unless rows.is_a?(Array)
|
|
79
|
+
|
|
80
|
+
rows.select { |row| row.is_a?(Hash) }.map do |row|
|
|
81
|
+
ir = row["isRead"]
|
|
82
|
+
Email.new(
|
|
83
|
+
id: row["id"].to_s, from_addr: (row["from_addr"] || "").strip, to: addr,
|
|
84
|
+
subject: (row["subject"] || "").strip, text: (row["description"] || "").strip,
|
|
85
|
+
html: "", date: cx_date_to_iso(row["date"] || ""),
|
|
86
|
+
is_read: ir == true || ir == 1, attachments: []
|
|
87
|
+
)
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "securerandom"
|
|
5
|
+
|
|
6
|
+
module TempmailSdk
|
|
7
|
+
module Providers
|
|
8
|
+
# maildrop.cc GraphQL API 渠道 — https://maildrop.cc
|
|
9
|
+
# 无认证,公共邮箱
|
|
10
|
+
module MaildropCc
|
|
11
|
+
CHANNEL = "maildrop-cc"
|
|
12
|
+
DOMAIN = "maildrop.cc"
|
|
13
|
+
GRAPHQL_URL = "https://api.maildrop.cc/graphql"
|
|
14
|
+
|
|
15
|
+
HEADERS = {
|
|
16
|
+
"Accept" => "application/json",
|
|
17
|
+
"Content-Type" => "application/json",
|
|
18
|
+
"Origin" => "https://maildrop.cc",
|
|
19
|
+
"Referer" => "https://maildrop.cc/",
|
|
20
|
+
"User-Agent" => "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " \
|
|
21
|
+
"(KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36 Edg/146.0.0.0"
|
|
22
|
+
}.freeze
|
|
23
|
+
|
|
24
|
+
LOCAL_CHARS = ("a".."z").to_a + ("0".."9").to_a
|
|
25
|
+
|
|
26
|
+
module_function
|
|
27
|
+
|
|
28
|
+
def random_local(n = 10)
|
|
29
|
+
Array.new(n) { LOCAL_CHARS.sample }.join
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def mailbox(email)
|
|
33
|
+
e = (email || "").strip
|
|
34
|
+
idx = e.index("@")
|
|
35
|
+
idx ? e[0...idx] : e
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def do_graphql(query)
|
|
39
|
+
resp = Http.post(GRAPHQL_URL, headers: HEADERS, json: { "query" => query })
|
|
40
|
+
resp.raise_for_status
|
|
41
|
+
resp.json
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def inbox_query(mb)
|
|
45
|
+
mb_s = JSON.generate(mb)
|
|
46
|
+
"query { inbox(mailbox: #{mb_s}) { id headerfrom subject date } }"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def message_query(mb, mid)
|
|
50
|
+
mb_s = JSON.generate(mb)
|
|
51
|
+
mid_s = JSON.generate(mid)
|
|
52
|
+
"query { message(mailbox: #{mb_s}, id: #{mid_s}) { id headerfrom subject date html } }"
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# 创建 maildrop.cc 临时邮箱
|
|
56
|
+
# @return [EmailInfo]
|
|
57
|
+
def generate_email
|
|
58
|
+
email = "#{random_local(10)}@#{DOMAIN}"
|
|
59
|
+
EmailInfo.new(channel: CHANNEL, email: email, token: email)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# 获取邮件列表
|
|
63
|
+
# @param email [String]
|
|
64
|
+
# @param token [String] (不使用,公共邮箱无需认证)
|
|
65
|
+
# @return [Array<Email>]
|
|
66
|
+
def get_emails(email, token = "")
|
|
67
|
+
mb = mailbox(email)
|
|
68
|
+
raise "maildrop-cc: 邮箱地址为空" if mb.empty?
|
|
69
|
+
|
|
70
|
+
inbox_resp = do_graphql(inbox_query(mb))
|
|
71
|
+
items = ((inbox_resp || {})["data"] || {})["inbox"]
|
|
72
|
+
return [] unless items.is_a?(Array) && !items.empty?
|
|
73
|
+
|
|
74
|
+
items.filter_map do |item|
|
|
75
|
+
next unless item.is_a?(Hash)
|
|
76
|
+
|
|
77
|
+
mid = (item["id"] || "").to_s
|
|
78
|
+
|
|
79
|
+
raw = {
|
|
80
|
+
"id" => mid,
|
|
81
|
+
"from" => item["headerfrom"] || "",
|
|
82
|
+
"subject" => item["subject"] || "",
|
|
83
|
+
"date" => item["date"] || ""
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
# 查询单封详情补全 html 正文
|
|
87
|
+
unless mid.empty?
|
|
88
|
+
begin
|
|
89
|
+
msg_resp = do_graphql(message_query(mb, mid))
|
|
90
|
+
msg = ((msg_resp || {})["data"] || {})["message"]
|
|
91
|
+
if msg.is_a?(Hash)
|
|
92
|
+
raw = {
|
|
93
|
+
"id" => msg["id"] || mid,
|
|
94
|
+
"from" => msg["headerfrom"] || "",
|
|
95
|
+
"subject" => msg["subject"] || "",
|
|
96
|
+
"date" => msg["date"] || "",
|
|
97
|
+
"html" => msg["html"] || ""
|
|
98
|
+
}
|
|
99
|
+
end
|
|
100
|
+
rescue StandardError
|
|
101
|
+
# 回退使用列表元信息
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
Normalize.normalize_email(raw, email)
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "uri"
|
|
4
|
+
|
|
5
|
+
module TempmailSdk
|
|
6
|
+
module Providers
|
|
7
|
+
# MailForSpam 渠道 — https://mailforspam.com
|
|
8
|
+
# 匿名 REST API:邮箱列表接口 + 逐封详情
|
|
9
|
+
module Mailforspam
|
|
10
|
+
CHANNEL = "mailforspam"
|
|
11
|
+
API_BASE = "https://api.mailforspam.com/api"
|
|
12
|
+
DEFAULT_DOMAIN = "mailforspam.com"
|
|
13
|
+
DOMAINS = %w[mailforspam.com tempmail.io disposable.email].freeze
|
|
14
|
+
HEADERS = {
|
|
15
|
+
"Accept" => "application/json",
|
|
16
|
+
"Referer" => "https://mailforspam.com/",
|
|
17
|
+
"Origin" => "https://mailforspam.com",
|
|
18
|
+
"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"
|
|
19
|
+
}.freeze
|
|
20
|
+
|
|
21
|
+
module_function
|
|
22
|
+
|
|
23
|
+
def random_local
|
|
24
|
+
chars = ("a".."z").to_a + ("0".."9").to_a
|
|
25
|
+
"sdk#{Array.new(16) { chars.sample }.join}"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# 选择域名:命中别名域名则用之,否则默认域名
|
|
29
|
+
def pick_domain(preferred)
|
|
30
|
+
p = preferred.to_s.strip.sub(/\A@/, "").downcase
|
|
31
|
+
unless p.empty?
|
|
32
|
+
found = DOMAINS.find { |d| d.downcase == p }
|
|
33
|
+
return found if found
|
|
34
|
+
end
|
|
35
|
+
DEFAULT_DOMAIN
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def mailbox_emails_url(email, page_size = 100)
|
|
39
|
+
"#{API_BASE}/mailboxes/#{URI.encode_www_form_component(email)}/emails" \
|
|
40
|
+
"?page=1&page_size=#{page_size}&sort_by=date&sort_order=desc"
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def flatten(raw, recipient)
|
|
44
|
+
{
|
|
45
|
+
"id" => raw["id"] || "", "from" => raw["sender"] || "",
|
|
46
|
+
"to" => raw["receiver"] || recipient, "subject" => raw["subject"] || "",
|
|
47
|
+
"text" => raw["body_text"] || "", "html" => raw["body_html"] || "",
|
|
48
|
+
"date" => raw["date"] || "", "isRead" => !raw["readAt"].nil?,
|
|
49
|
+
"attachments" => raw["attachments"]
|
|
50
|
+
}
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def fetch_message(message_id, email)
|
|
54
|
+
resp = Http.get(
|
|
55
|
+
"#{API_BASE}/mailboxes/#{URI.encode_www_form_component(email)}/emails/#{URI.encode_www_form_component(message_id)}",
|
|
56
|
+
headers: HEADERS, timeout: 15
|
|
57
|
+
)
|
|
58
|
+
return nil unless resp.ok?
|
|
59
|
+
|
|
60
|
+
data = resp.json
|
|
61
|
+
data.is_a?(Hash) ? data : nil
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# @param domain [String, nil]
|
|
65
|
+
# @return [EmailInfo]
|
|
66
|
+
def generate_email(domain = nil)
|
|
67
|
+
email = "#{random_local}@#{pick_domain(domain)}"
|
|
68
|
+
Http.get(mailbox_emails_url(email, 1), headers: HEADERS, timeout: 15).raise_for_status
|
|
69
|
+
EmailInfo.new(channel: CHANNEL, email: email)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# @param email [String]
|
|
73
|
+
# @return [Array<Email>]
|
|
74
|
+
def get_emails(email)
|
|
75
|
+
address = email.to_s.strip
|
|
76
|
+
raise "mailforspam: empty email" if address.empty?
|
|
77
|
+
|
|
78
|
+
resp = Http.get(mailbox_emails_url(address, 100), headers: HEADERS, timeout: 15)
|
|
79
|
+
resp.raise_for_status
|
|
80
|
+
data = resp.json
|
|
81
|
+
rows = data.is_a?(Hash) ? data["emails"] : nil
|
|
82
|
+
return [] unless rows.is_a?(Array)
|
|
83
|
+
|
|
84
|
+
rows.filter_map do |item|
|
|
85
|
+
next unless item.is_a?(Hash)
|
|
86
|
+
|
|
87
|
+
message_id = item["id"].to_s.strip
|
|
88
|
+
next if message_id.empty?
|
|
89
|
+
|
|
90
|
+
detail = fetch_message(message_id, address)
|
|
91
|
+
Normalize.normalize_email(flatten(detail || item, address), address)
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module TempmailSdk
|
|
4
|
+
module Providers
|
|
5
|
+
# MailholeDe 渠道实现
|
|
6
|
+
# API: https://mailhole.de
|
|
7
|
+
# 公共临时邮箱,无需认证,token 即邮箱地址本身
|
|
8
|
+
module MailholeDe
|
|
9
|
+
CHANNEL = "mailhole-de"
|
|
10
|
+
BASE_URL = "https://mailhole.de"
|
|
11
|
+
|
|
12
|
+
# 从 /api/random 返回的 HTML 中提取邮箱地址
|
|
13
|
+
EMAIL_RE = /([a-z0-9.]+@mailhole\.de)/
|
|
14
|
+
|
|
15
|
+
module_function
|
|
16
|
+
|
|
17
|
+
# 按优先级从消息字典中提取首个非空字符串值
|
|
18
|
+
def first_str(msg, *keys)
|
|
19
|
+
keys.each do |key|
|
|
20
|
+
val = msg[key]
|
|
21
|
+
return val.to_s if val.is_a?(String) && !val.empty?
|
|
22
|
+
end
|
|
23
|
+
""
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# 创建 mailhole.de 临时邮箱
|
|
27
|
+
# @return [EmailInfo]
|
|
28
|
+
def generate_email
|
|
29
|
+
resp = Http.get("#{BASE_URL}/api/random", headers: { "Accept" => "text/html" }, timeout: 15)
|
|
30
|
+
resp.raise_for_status
|
|
31
|
+
|
|
32
|
+
m = resp.body.match(EMAIL_RE)
|
|
33
|
+
raise "mailhole-de: 无法从响应中解析邮箱地址" unless m
|
|
34
|
+
|
|
35
|
+
email = m[1]
|
|
36
|
+
EmailInfo.new(channel: CHANNEL, email: email, token: email)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# 获取邮件列表
|
|
40
|
+
# @param email [String]
|
|
41
|
+
# @return [Array<Email>]
|
|
42
|
+
def get_emails(email)
|
|
43
|
+
addr = email.to_s.strip
|
|
44
|
+
raise "mailhole-de: 缺少邮箱地址" if addr.empty?
|
|
45
|
+
|
|
46
|
+
resp = Http.get("#{BASE_URL}/json/#{URI.encode_www_form_component(addr)}",
|
|
47
|
+
headers: { "Accept" => "application/json" }, timeout: 15)
|
|
48
|
+
resp.raise_for_status
|
|
49
|
+
|
|
50
|
+
return [] if resp.body.nil? || resp.body.strip.empty? || resp.body.strip == "[]"
|
|
51
|
+
|
|
52
|
+
messages = resp.json
|
|
53
|
+
return [] unless messages.is_a?(Array)
|
|
54
|
+
|
|
55
|
+
messages.select { |msg| msg.is_a?(Hash) }.map do |msg|
|
|
56
|
+
row = {
|
|
57
|
+
"id" => (msg["id"] || "").to_s,
|
|
58
|
+
"from" => first_str(msg, "sender", "from"),
|
|
59
|
+
"to" => addr,
|
|
60
|
+
"subject" => (msg["subject"] || "").to_s,
|
|
61
|
+
"text" => first_str(msg, "body", "text"),
|
|
62
|
+
"html" => first_str(msg, "html", "body"),
|
|
63
|
+
"received_at" => first_str(msg, "date", "received")
|
|
64
|
+
}
|
|
65
|
+
Normalize.normalize_email(row, addr)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|