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,67 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module TempmailSdk
|
|
4
|
+
module Providers
|
|
5
|
+
# temp-mail.io 渠道实现
|
|
6
|
+
# API: https://api.internal.temp-mail.io/api/v3
|
|
7
|
+
module TempMailIo
|
|
8
|
+
CHANNEL = "temp-mail-io"
|
|
9
|
+
BASE_URL = "https://api.internal.temp-mail.io/api/v3"
|
|
10
|
+
PAGE_URL = "https://temp-mail.io/en"
|
|
11
|
+
UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36"
|
|
12
|
+
|
|
13
|
+
@cors_header = nil
|
|
14
|
+
|
|
15
|
+
module_function
|
|
16
|
+
|
|
17
|
+
def fetch_cors_header
|
|
18
|
+
return @cors_header if @cors_header
|
|
19
|
+
|
|
20
|
+
begin
|
|
21
|
+
resp = Http.get(PAGE_URL, headers: { "User-Agent" => UA }, timeout: 15)
|
|
22
|
+
m = resp.body.match(/mobileTestingHeader\s*:\s*"([^"]+)"/)
|
|
23
|
+
@cors_header = m ? m[1] : "1"
|
|
24
|
+
rescue StandardError
|
|
25
|
+
@cors_header = "1"
|
|
26
|
+
end
|
|
27
|
+
@cors_header
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def headers
|
|
31
|
+
{
|
|
32
|
+
"Content-Type" => "application/json",
|
|
33
|
+
"Application-Name" => "web",
|
|
34
|
+
"Application-Version" => "4.0.0",
|
|
35
|
+
"X-CORS-Header" => fetch_cors_header,
|
|
36
|
+
"User-Agent" => UA,
|
|
37
|
+
"Origin" => "https://temp-mail.io",
|
|
38
|
+
"Referer" => "https://temp-mail.io/"
|
|
39
|
+
}
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# @return [EmailInfo]
|
|
43
|
+
def generate_email
|
|
44
|
+
resp = Http.post("#{BASE_URL}/email/new", headers: headers,
|
|
45
|
+
json: { "min_name_length" => 10, "max_name_length" => 10 }, timeout: 15)
|
|
46
|
+
resp.raise_for_status
|
|
47
|
+
data = resp.json
|
|
48
|
+
email = data.is_a?(Hash) ? data["email"] : nil
|
|
49
|
+
token = data.is_a?(Hash) ? data["token"] : nil
|
|
50
|
+
raise "temp-mail-io: invalid generate response" if email.to_s.empty? || token.to_s.empty?
|
|
51
|
+
|
|
52
|
+
EmailInfo.new(channel: CHANNEL, email: email, token: token)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# @param email [String]
|
|
56
|
+
# @return [Array<Email>]
|
|
57
|
+
def get_emails(email)
|
|
58
|
+
resp = Http.get("#{BASE_URL}/email/#{email}/messages", headers: headers, timeout: 15)
|
|
59
|
+
resp.raise_for_status
|
|
60
|
+
rows = resp.json
|
|
61
|
+
return [] unless rows.is_a?(Array)
|
|
62
|
+
|
|
63
|
+
rows.select { |r| r.is_a?(Hash) }.map { |raw| Normalize.normalize_email(raw, email) }
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module TempmailSdk
|
|
4
|
+
module Providers
|
|
5
|
+
# Temp-Mail.Now 渠道 — https://temp-mail.now
|
|
6
|
+
# 基于会话 Cookie 的临时邮箱服务
|
|
7
|
+
module TempMailNow
|
|
8
|
+
CHANNEL = "temp-mail-now"
|
|
9
|
+
BASE_URL = "https://temp-mail.now"
|
|
10
|
+
|
|
11
|
+
PAGE_HEADERS = {
|
|
12
|
+
"Accept" => "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
|
|
13
|
+
"User-Agent" => "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
|
|
14
|
+
}.freeze
|
|
15
|
+
|
|
16
|
+
API_HEADERS = {
|
|
17
|
+
"Accept" => "application/json, text/javascript, */*; q=0.01",
|
|
18
|
+
"X-Requested-With" => "XMLHttpRequest",
|
|
19
|
+
"Referer" => "#{BASE_URL}/en/",
|
|
20
|
+
"User-Agent" => "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
|
|
21
|
+
}.freeze
|
|
22
|
+
|
|
23
|
+
module_function
|
|
24
|
+
|
|
25
|
+
def extract_cookies(resp)
|
|
26
|
+
cookies = {}
|
|
27
|
+
resp.set_cookies.each do |line|
|
|
28
|
+
pair = line.split(";").first.to_s.strip
|
|
29
|
+
k, v = pair.split("=", 2)
|
|
30
|
+
cookies[k] = v if k && v
|
|
31
|
+
end
|
|
32
|
+
cookies
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def cookies_to_str(cookies)
|
|
36
|
+
cookies.map { |k, v| "#{k}=#{v}" }.join("; ")
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# 创建临时邮箱
|
|
40
|
+
# @return [EmailInfo]
|
|
41
|
+
def generate_email
|
|
42
|
+
# 获取 session cookie
|
|
43
|
+
resp = Http.get("#{BASE_URL}/en/", headers: PAGE_HEADERS, timeout: 15)
|
|
44
|
+
resp.raise_for_status
|
|
45
|
+
cookies = extract_cookies(resp)
|
|
46
|
+
cookie_str = cookies_to_str(cookies)
|
|
47
|
+
raise "temp-mail-now: 无法获取 session cookie" if cookie_str.empty?
|
|
48
|
+
|
|
49
|
+
# 创建新邮箱
|
|
50
|
+
resp2 = Http.post("#{BASE_URL}/change_email",
|
|
51
|
+
headers: API_HEADERS.merge("Cookie" => cookie_str), timeout: 15)
|
|
52
|
+
resp2.raise_for_status
|
|
53
|
+
|
|
54
|
+
# 合并可能更新的 cookie
|
|
55
|
+
extract_cookies(resp2).each { |k, v| cookies[k] = v }
|
|
56
|
+
cookie_str = cookies_to_str(cookies)
|
|
57
|
+
|
|
58
|
+
data = resp2.json
|
|
59
|
+
raise "temp-mail-now: 响应格式无效" unless data.is_a?(Hash)
|
|
60
|
+
|
|
61
|
+
address = data["new_email"] || ""
|
|
62
|
+
raise "temp-mail-now: 创建邮箱失败" if address.empty? || !address.include?("@")
|
|
63
|
+
|
|
64
|
+
EmailInfo.new(channel: CHANNEL, email: address, token: cookie_str)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# 获取邮件列表
|
|
68
|
+
# @param token [String] session cookie
|
|
69
|
+
# @param email [String]
|
|
70
|
+
# @return [Array<Email>]
|
|
71
|
+
def get_emails(token, email)
|
|
72
|
+
raise "temp-mail-now: session cookie 不能为空" if token.nil? || token.empty?
|
|
73
|
+
|
|
74
|
+
addr = (email || "").strip
|
|
75
|
+
raise "temp-mail-now: 邮箱地址不能为空" if addr.empty?
|
|
76
|
+
|
|
77
|
+
resp = Http.get("#{BASE_URL}/fetch_emails",
|
|
78
|
+
headers: API_HEADERS.merge("Cookie" => token), timeout: 15)
|
|
79
|
+
resp.raise_for_status
|
|
80
|
+
data = resp.json
|
|
81
|
+
return [] unless data.is_a?(Hash)
|
|
82
|
+
|
|
83
|
+
emails_list = data["emails"]
|
|
84
|
+
return [] unless emails_list.is_a?(Array)
|
|
85
|
+
|
|
86
|
+
emails_list.filter_map do |item|
|
|
87
|
+
next unless item.is_a?(Hash)
|
|
88
|
+
|
|
89
|
+
raw = {
|
|
90
|
+
"id" => item["id"] || "",
|
|
91
|
+
"from" => item["from"] || item["from_address"] || item["sender"] || "",
|
|
92
|
+
"to" => item["to"] || addr,
|
|
93
|
+
"subject" => item["subject"] || "",
|
|
94
|
+
"text" => item["text"] || item["body_text"] || "",
|
|
95
|
+
"html" => item["html"] || item["body_html"] || "",
|
|
96
|
+
"date" => item["date"] || item["received_at"] || ""
|
|
97
|
+
}
|
|
98
|
+
Normalize.normalize_email(raw, addr)
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "uri"
|
|
4
|
+
|
|
5
|
+
module TempmailSdk
|
|
6
|
+
module Providers
|
|
7
|
+
# temp-mail.org 渠道 — https://temp-mail.org
|
|
8
|
+
# API Base: https://web2.temp-mail.org
|
|
9
|
+
module TempMailOrg
|
|
10
|
+
CHANNEL = "temp-mail-org"
|
|
11
|
+
BASE_URL = "https://web2.temp-mail.org"
|
|
12
|
+
|
|
13
|
+
HEADERS = {
|
|
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
|
+
"Origin" => "https://temp-mail.org",
|
|
16
|
+
"Referer" => "https://temp-mail.org/"
|
|
17
|
+
}.freeze
|
|
18
|
+
|
|
19
|
+
module_function
|
|
20
|
+
|
|
21
|
+
def auth_headers(token)
|
|
22
|
+
HEADERS.merge("Authorization" => "Bearer #{token}")
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# 创建临时邮箱
|
|
26
|
+
# @return [EmailInfo]
|
|
27
|
+
def generate_email
|
|
28
|
+
resp = Http.post("#{BASE_URL}/mailbox", headers: HEADERS, timeout: 15)
|
|
29
|
+
resp.raise_for_status
|
|
30
|
+
data = resp.json
|
|
31
|
+
raise "temp-mail-org: 响应格式无效" unless data.is_a?(Hash)
|
|
32
|
+
|
|
33
|
+
token = data["token"] || ""
|
|
34
|
+
mailbox = data["mailbox"] || ""
|
|
35
|
+
raise "temp-mail-org: 创建邮箱失败" if token.empty? || mailbox.empty?
|
|
36
|
+
|
|
37
|
+
EmailInfo.new(channel: CHANNEL, email: mailbox, token: token)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# 获取邮件列表
|
|
41
|
+
# @param token [String] JWT 令牌
|
|
42
|
+
# @param email [String]
|
|
43
|
+
# @return [Array<Email>]
|
|
44
|
+
def get_emails(token, email)
|
|
45
|
+
raise "temp-mail-org: token 不能为空" if token.nil? || token.empty?
|
|
46
|
+
|
|
47
|
+
addr = (email || "").strip
|
|
48
|
+
raise "temp-mail-org: 邮箱地址不能为空" if addr.empty?
|
|
49
|
+
|
|
50
|
+
resp = Http.get("#{BASE_URL}/messages", headers: auth_headers(token), timeout: 15)
|
|
51
|
+
resp.raise_for_status
|
|
52
|
+
data = resp.json
|
|
53
|
+
return [] unless data.is_a?(Hash)
|
|
54
|
+
|
|
55
|
+
messages = data["messages"]
|
|
56
|
+
return [] unless messages.is_a?(Array) && !messages.empty?
|
|
57
|
+
|
|
58
|
+
messages.filter_map do |msg|
|
|
59
|
+
next unless msg.is_a?(Hash)
|
|
60
|
+
|
|
61
|
+
msg_id = msg["_id"] || ""
|
|
62
|
+
next if msg_id.empty?
|
|
63
|
+
|
|
64
|
+
detail = fetch_message_detail(token, msg_id)
|
|
65
|
+
raw = {
|
|
66
|
+
"id" => msg_id,
|
|
67
|
+
"from" => detail["from"] || msg["from"] || "",
|
|
68
|
+
"to" => addr,
|
|
69
|
+
"subject" => detail["subject"] || msg["subject"] || "",
|
|
70
|
+
"text" => detail["bodyPreview"] || msg["bodyPreview"] || "",
|
|
71
|
+
"html" => detail["bodyHtml"] || "",
|
|
72
|
+
"date" => detail["createdAt"] || msg["receivedAt"].to_s
|
|
73
|
+
}
|
|
74
|
+
Normalize.normalize_email(raw, addr)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def fetch_message_detail(token, msg_id)
|
|
79
|
+
resp = Http.get("#{BASE_URL}/messages/#{msg_id}", headers: auth_headers(token), timeout: 15)
|
|
80
|
+
return {} unless resp.ok?
|
|
81
|
+
|
|
82
|
+
data = resp.json
|
|
83
|
+
data.is_a?(Hash) ? data : {}
|
|
84
|
+
rescue StandardError
|
|
85
|
+
{}
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module TempmailSdk
|
|
6
|
+
module Providers
|
|
7
|
+
# tempemail.co 渠道 — https://tempemail.co
|
|
8
|
+
# Cookie Session + REST JSON API
|
|
9
|
+
module TempemailCo
|
|
10
|
+
CHANNEL = "tempemail-co"
|
|
11
|
+
BASE_URL = "https://tempemail.co"
|
|
12
|
+
|
|
13
|
+
# 从邮件列表 HTML 中提取邮件 ID(data-id="数字")
|
|
14
|
+
MAIL_ID_RE = /data-id="(\d+)"/
|
|
15
|
+
|
|
16
|
+
HEADERS = {
|
|
17
|
+
"Accept" => "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
|
|
18
|
+
"Accept-Language" => "en-US,en;q=0.9",
|
|
19
|
+
"Cache-Control" => "no-cache",
|
|
20
|
+
"DNT" => "1",
|
|
21
|
+
"Pragma" => "no-cache",
|
|
22
|
+
"Upgrade-Insecure-Requests" => "1",
|
|
23
|
+
"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"
|
|
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 cookies_to_str(cookies)
|
|
39
|
+
cookies.sort.map { |k, v| "#{k}=#{v}" }.join("; ")
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def encode_token(address, cookies)
|
|
43
|
+
JSON.generate({ "address" => address, "cookies" => cookies })
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def decode_token(token)
|
|
47
|
+
data = JSON.parse(token)
|
|
48
|
+
address = data["address"] || ""
|
|
49
|
+
cookies = data["cookies"] || {}
|
|
50
|
+
raise "tempemail-co: token 数据不完整" if address.empty? || !cookies.is_a?(Hash)
|
|
51
|
+
|
|
52
|
+
[address, cookies]
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# 创建临时邮箱
|
|
56
|
+
# @return [EmailInfo]
|
|
57
|
+
def generate_email
|
|
58
|
+
r = Http.get("#{BASE_URL}/mail/random", headers: HEADERS.merge(
|
|
59
|
+
"Referer" => "#{BASE_URL}/",
|
|
60
|
+
"Accept" => "application/json, text/javascript, */*; q=0.01"
|
|
61
|
+
))
|
|
62
|
+
r.raise_for_status
|
|
63
|
+
data = r.json
|
|
64
|
+
raise "tempemail-co: 创建邮箱失败" unless data["result"]
|
|
65
|
+
|
|
66
|
+
address = (data["address"] || data["id"] || "").strip
|
|
67
|
+
raise "tempemail-co: 返回的邮箱地址无效" if address.empty? || !address.include?("@")
|
|
68
|
+
|
|
69
|
+
cookies = extract_cookies(r)
|
|
70
|
+
tok = encode_token(address, cookies)
|
|
71
|
+
EmailInfo.new(channel: CHANNEL, email: address, token: tok)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# 获取邮件列表
|
|
75
|
+
# @param email [String]
|
|
76
|
+
# @param token [String]
|
|
77
|
+
# @return [Array<Email>]
|
|
78
|
+
def get_emails(email, token)
|
|
79
|
+
raise "tempemail-co: token 不能为空" if token.nil? || token.empty?
|
|
80
|
+
|
|
81
|
+
addr = (email || "").strip
|
|
82
|
+
raise "tempemail-co: 邮箱地址不能为空" if addr.empty?
|
|
83
|
+
|
|
84
|
+
address, cookies = decode_token(token)
|
|
85
|
+
cookie_str = cookies_to_str(cookies)
|
|
86
|
+
|
|
87
|
+
# 获取邮件列表
|
|
88
|
+
r = Http.get("#{BASE_URL}/get-mails?mail_id=#{URI.encode_www_form_component(address)}&unseen=0&is_new=1",
|
|
89
|
+
headers: HEADERS.merge(
|
|
90
|
+
"Referer" => "#{BASE_URL}/",
|
|
91
|
+
"Cookie" => cookie_str,
|
|
92
|
+
"Accept" => "application/json, text/javascript, */*; q=0.01"
|
|
93
|
+
))
|
|
94
|
+
r.raise_for_status
|
|
95
|
+
data = r.json
|
|
96
|
+
count = data["count"] || 0
|
|
97
|
+
return [] if count <= 0
|
|
98
|
+
|
|
99
|
+
mails_html = data["mails"] || ""
|
|
100
|
+
return [] unless mails_html.is_a?(String) && !mails_html.empty?
|
|
101
|
+
|
|
102
|
+
mail_ids = mails_html.scan(MAIL_ID_RE).flatten.uniq
|
|
103
|
+
return [] if mail_ids.empty?
|
|
104
|
+
|
|
105
|
+
mail_ids.filter_map do |mail_id|
|
|
106
|
+
begin
|
|
107
|
+
detail_resp = Http.get("#{BASE_URL}/mail/info?id=#{mail_id}",
|
|
108
|
+
headers: HEADERS.merge(
|
|
109
|
+
"Referer" => "#{BASE_URL}/",
|
|
110
|
+
"Cookie" => cookie_str,
|
|
111
|
+
"Accept" => "application/json, text/javascript, */*; q=0.01"
|
|
112
|
+
))
|
|
113
|
+
next unless detail_resp.ok?
|
|
114
|
+
|
|
115
|
+
detail_data = detail_resp.json
|
|
116
|
+
next unless detail_data["result"]
|
|
117
|
+
|
|
118
|
+
mail_info = detail_data["mail"]
|
|
119
|
+
next unless mail_info.is_a?(Hash)
|
|
120
|
+
|
|
121
|
+
from_name = (mail_info["fromName"] || "").strip
|
|
122
|
+
from_addr = (mail_info["fromAddress"] || "").strip
|
|
123
|
+
from_display = from_name.empty? ? from_addr : "#{from_name} <#{from_addr}>"
|
|
124
|
+
|
|
125
|
+
raw = {
|
|
126
|
+
"id" => mail_id.to_s,
|
|
127
|
+
"from" => from_display,
|
|
128
|
+
"from_address" => from_addr,
|
|
129
|
+
"to" => addr,
|
|
130
|
+
"subject" => mail_info["subject"] || "",
|
|
131
|
+
"html" => mail_info["textHtml"] || "",
|
|
132
|
+
"date" => mail_info["displayDate"] || ""
|
|
133
|
+
}
|
|
134
|
+
Normalize.normalize_email(raw, addr)
|
|
135
|
+
rescue StandardError
|
|
136
|
+
next
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "base64"
|
|
4
|
+
require "json"
|
|
5
|
+
|
|
6
|
+
module TempmailSdk
|
|
7
|
+
module Providers
|
|
8
|
+
# tempemail.info 临时邮箱渠道
|
|
9
|
+
# GET / 获取 PHPSESSID 会话 Cookie + base64 编码的邮箱地址
|
|
10
|
+
# POST /template/checker.php 获取邮件列表
|
|
11
|
+
# GET /view/{date} 获取单封邮件 HTML 正文
|
|
12
|
+
module TempemailInfo
|
|
13
|
+
CHANNEL = "tempemail-info"
|
|
14
|
+
BASE_URL = "https://tempemail.info"
|
|
15
|
+
|
|
16
|
+
# 匹配 var emailEncoded = "base64..."
|
|
17
|
+
EMAIL_RE = /var\s+emailEncoded\s*=\s*"([^"]+)"/
|
|
18
|
+
|
|
19
|
+
UA = "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
|
+
|
|
21
|
+
module_function
|
|
22
|
+
|
|
23
|
+
def base_headers
|
|
24
|
+
{
|
|
25
|
+
"User-Agent" => UA,
|
|
26
|
+
"Accept-Language" => "zh-CN,zh;q=0.9,en;q=0.8",
|
|
27
|
+
"Referer" => "#{BASE_URL}/",
|
|
28
|
+
"Origin" => BASE_URL
|
|
29
|
+
}
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def extract_cookie_header(resp)
|
|
33
|
+
cookies = {}
|
|
34
|
+
resp.set_cookies.each do |line|
|
|
35
|
+
pair = line.split(";").first.to_s.strip
|
|
36
|
+
k, v = pair.split("=", 2)
|
|
37
|
+
cookies[k] = v if k && v
|
|
38
|
+
end
|
|
39
|
+
cookies.sort.map { |k, v| "#{k}=#{v}" }.join("; ")
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# 创建临时邮箱
|
|
43
|
+
# @return [EmailInfo]
|
|
44
|
+
def generate_email
|
|
45
|
+
headers = base_headers.merge("Accept" => "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
|
|
46
|
+
resp = Http.get("#{BASE_URL}/", headers: headers)
|
|
47
|
+
resp.raise_for_status
|
|
48
|
+
|
|
49
|
+
cookie_hdr = extract_cookie_header(resp)
|
|
50
|
+
raise "tempemail-info: 未获取到会话 Cookie" if cookie_hdr.empty?
|
|
51
|
+
|
|
52
|
+
m = resp.body.match(EMAIL_RE)
|
|
53
|
+
raise "tempemail-info: 未在页面中找到 emailEncoded" unless m
|
|
54
|
+
|
|
55
|
+
decoded = Base64.decode64(m[1]).force_encoding("UTF-8").strip
|
|
56
|
+
raise "tempemail-info: 解码出的邮箱地址无效" if decoded.empty? || !decoded.include?("@")
|
|
57
|
+
|
|
58
|
+
EmailInfo.new(channel: CHANNEL, email: decoded, token: cookie_hdr)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# 获取邮件列表
|
|
62
|
+
# @param email [String]
|
|
63
|
+
# @param token [String] 会话 Cookie 串
|
|
64
|
+
# @return [Array<Email>]
|
|
65
|
+
def get_emails(email, token)
|
|
66
|
+
cookie_hdr = (token || "").strip
|
|
67
|
+
raise "tempemail-info: 缺少会话 Cookie" if cookie_hdr.empty?
|
|
68
|
+
|
|
69
|
+
headers = base_headers.merge(
|
|
70
|
+
"Accept" => "application/json, text/javascript, */*; q=0.01",
|
|
71
|
+
"X-Requested-With" => "XMLHttpRequest",
|
|
72
|
+
"Content-Type" => "application/x-www-form-urlencoded",
|
|
73
|
+
"Cookie" => cookie_hdr
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
resp = Http.post("#{BASE_URL}/template/checker.php", headers: headers, body: "last_id=0")
|
|
77
|
+
resp.raise_for_status
|
|
78
|
+
|
|
79
|
+
rows = resp.json
|
|
80
|
+
return [] unless rows.is_a?(Array) && !rows.empty?
|
|
81
|
+
|
|
82
|
+
rows.filter_map do |row|
|
|
83
|
+
next unless row.is_a?(Hash)
|
|
84
|
+
|
|
85
|
+
from_addr = (row["from"] || "").to_s
|
|
86
|
+
name = (row["name"] || "").to_s
|
|
87
|
+
from_addr = "#{name} <#{from_addr}>" if !name.empty? && name != from_addr
|
|
88
|
+
|
|
89
|
+
date = (row["date"] || "").to_s
|
|
90
|
+
html_body = fetch_body(cookie_hdr, date)
|
|
91
|
+
|
|
92
|
+
raw = {
|
|
93
|
+
"id" => (row["id"] || "").to_s,
|
|
94
|
+
"from" => from_addr,
|
|
95
|
+
"to" => email,
|
|
96
|
+
"subject" => row["subject"] || "",
|
|
97
|
+
"date" => date,
|
|
98
|
+
"html" => html_body,
|
|
99
|
+
"isRead" => !!row["read"]
|
|
100
|
+
}
|
|
101
|
+
Normalize.normalize_email(raw, email)
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def fetch_body(cookie_hdr, date)
|
|
106
|
+
return "" if date.nil? || date.to_s.strip.empty?
|
|
107
|
+
|
|
108
|
+
headers = base_headers.merge(
|
|
109
|
+
"Accept" => "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
|
|
110
|
+
"Cookie" => cookie_hdr
|
|
111
|
+
)
|
|
112
|
+
resp = Http.get("#{BASE_URL}/view/#{URI.encode_www_form_component(date)}", headers: headers)
|
|
113
|
+
return "" unless resp.ok?
|
|
114
|
+
|
|
115
|
+
resp.body
|
|
116
|
+
rescue StandardError
|
|
117
|
+
""
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module TempmailSdk
|
|
6
|
+
module Providers
|
|
7
|
+
# tempemails.net 渠道 — https://tempemails.net
|
|
8
|
+
# Laravel Cookie Session + CSRF + REST JSON API
|
|
9
|
+
module TempemailsNet
|
|
10
|
+
CHANNEL = "tempemails-net"
|
|
11
|
+
BASE_URL = "https://tempemails.net"
|
|
12
|
+
|
|
13
|
+
# 从 HTML 中提取 CSRF token
|
|
14
|
+
CSRF_RE = /<meta\s+name="csrf-token"\s+content="([^"]+)"/
|
|
15
|
+
|
|
16
|
+
UA = "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"
|
|
17
|
+
|
|
18
|
+
module_function
|
|
19
|
+
|
|
20
|
+
def build_headers(extra = {})
|
|
21
|
+
h = {
|
|
22
|
+
"Accept" => "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
|
|
23
|
+
"Accept-Language" => "en-US,en;q=0.9",
|
|
24
|
+
"Cache-Control" => "no-cache",
|
|
25
|
+
"DNT" => "1",
|
|
26
|
+
"Pragma" => "no-cache",
|
|
27
|
+
"Upgrade-Insecure-Requests" => "1",
|
|
28
|
+
"User-Agent" => UA
|
|
29
|
+
}
|
|
30
|
+
h.merge(extra)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def extract_cookies(resp)
|
|
34
|
+
cookies = {}
|
|
35
|
+
resp.set_cookies.each do |line|
|
|
36
|
+
pair = line.split(";").first.to_s.strip
|
|
37
|
+
k, v = pair.split("=", 2)
|
|
38
|
+
cookies[k] = v if k && v
|
|
39
|
+
end
|
|
40
|
+
cookies
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def cookies_to_str(cookies)
|
|
44
|
+
cookies.sort.map { |k, v| "#{k}=#{v}" }.join("; ")
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def encode_token(csrf, cookies)
|
|
48
|
+
JSON.generate({ "csrf" => csrf, "cookies" => cookies })
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def decode_token(token)
|
|
52
|
+
data = JSON.parse(token)
|
|
53
|
+
csrf = data["csrf"] || ""
|
|
54
|
+
cookies = data["cookies"] || {}
|
|
55
|
+
raise "tempemails-net: token 数据不完整" if csrf.empty? || !cookies.is_a?(Hash)
|
|
56
|
+
|
|
57
|
+
[csrf, cookies]
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# 创建临时邮箱
|
|
61
|
+
# @return [EmailInfo]
|
|
62
|
+
def generate_email
|
|
63
|
+
# 获取首页,建立 session 并提取 CSRF
|
|
64
|
+
r = Http.get("#{BASE_URL}/", headers: build_headers)
|
|
65
|
+
r.raise_for_status
|
|
66
|
+
|
|
67
|
+
m = r.body.match(CSRF_RE)
|
|
68
|
+
raise "tempemails-net: 无法从首页提取 CSRF token" unless m
|
|
69
|
+
|
|
70
|
+
csrf = m[1]
|
|
71
|
+
cookies = extract_cookies(r)
|
|
72
|
+
cookie_str = cookies_to_str(cookies)
|
|
73
|
+
|
|
74
|
+
# POST /get_messages 获取自动分配的邮箱
|
|
75
|
+
r2 = Http.post("#{BASE_URL}/get_messages", headers: build_headers(
|
|
76
|
+
"Accept" => "application/json",
|
|
77
|
+
"X-Requested-With" => "XMLHttpRequest",
|
|
78
|
+
"X-CSRF-TOKEN" => csrf,
|
|
79
|
+
"Cookie" => cookie_str,
|
|
80
|
+
"Referer" => "#{BASE_URL}/"
|
|
81
|
+
))
|
|
82
|
+
r2.raise_for_status
|
|
83
|
+
data = r2.json
|
|
84
|
+
raise "tempemails-net: 获取邮箱失败" unless data["status"]
|
|
85
|
+
|
|
86
|
+
mailbox = (data["mailbox"] || "").strip
|
|
87
|
+
raise "tempemails-net: 返回的邮箱地址无效" if mailbox.empty? || !mailbox.include?("@")
|
|
88
|
+
|
|
89
|
+
merged_cookies = cookies.merge(extract_cookies(r2))
|
|
90
|
+
tok = encode_token(csrf, merged_cookies)
|
|
91
|
+
EmailInfo.new(channel: CHANNEL, email: mailbox, token: tok)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# 获取邮件列表
|
|
95
|
+
# @param email [String]
|
|
96
|
+
# @param token [String]
|
|
97
|
+
# @return [Array<Email>]
|
|
98
|
+
def get_emails(email, token)
|
|
99
|
+
raise "tempemails-net: token 不能为空" if token.nil? || token.empty?
|
|
100
|
+
|
|
101
|
+
addr = (email || "").strip
|
|
102
|
+
raise "tempemails-net: 邮箱地址不能为空" if addr.empty?
|
|
103
|
+
|
|
104
|
+
csrf, cookies = decode_token(token)
|
|
105
|
+
cookie_str = cookies_to_str(cookies)
|
|
106
|
+
|
|
107
|
+
r = Http.post("#{BASE_URL}/get_messages", headers: build_headers(
|
|
108
|
+
"Accept" => "application/json",
|
|
109
|
+
"X-Requested-With" => "XMLHttpRequest",
|
|
110
|
+
"X-CSRF-TOKEN" => csrf,
|
|
111
|
+
"Cookie" => cookie_str,
|
|
112
|
+
"Referer" => "#{BASE_URL}/"
|
|
113
|
+
))
|
|
114
|
+
r.raise_for_status
|
|
115
|
+
data = r.json
|
|
116
|
+
|
|
117
|
+
messages = data["messages"]
|
|
118
|
+
return [] unless messages.is_a?(Array) && !messages.empty?
|
|
119
|
+
|
|
120
|
+
messages.filter_map do |msg|
|
|
121
|
+
next unless msg.is_a?(Hash)
|
|
122
|
+
|
|
123
|
+
mail_id = msg["id"] || ""
|
|
124
|
+
html_body = ""
|
|
125
|
+
unless mail_id.to_s.empty?
|
|
126
|
+
begin
|
|
127
|
+
view_resp = Http.get("#{BASE_URL}/view/#{mail_id}", headers: build_headers(
|
|
128
|
+
"Cookie" => cookie_str,
|
|
129
|
+
"Referer" => "#{BASE_URL}/"
|
|
130
|
+
))
|
|
131
|
+
html_body = view_resp.body if view_resp.ok?
|
|
132
|
+
rescue StandardError
|
|
133
|
+
# 单封邮件正文获取失败不影响其他
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
from_name = (msg["from"] || "").strip
|
|
138
|
+
from_addr = (msg["from_email"] || "").strip
|
|
139
|
+
from_display = if !from_name.empty? && !from_addr.empty?
|
|
140
|
+
"#{from_name} <#{from_addr}>"
|
|
141
|
+
else
|
|
142
|
+
from_addr.empty? ? from_name : from_addr
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
raw = {
|
|
146
|
+
"id" => mail_id.to_s,
|
|
147
|
+
"from" => from_display,
|
|
148
|
+
"from_address" => from_addr,
|
|
149
|
+
"to" => addr,
|
|
150
|
+
"subject" => msg["subject"] || "",
|
|
151
|
+
"html" => html_body,
|
|
152
|
+
"date" => msg["receivedAt"] || ""
|
|
153
|
+
}
|
|
154
|
+
Normalize.normalize_email(raw, addr)
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|