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,80 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "uri"
|
|
4
|
+
|
|
5
|
+
module TempmailSdk
|
|
6
|
+
module Providers
|
|
7
|
+
# ThrowawayMail 渠道 — https://throwawaymail.app
|
|
8
|
+
# POST /api/mailboxes 创建邮箱,GET /api/mailboxes/{id}/messages 获取邮件
|
|
9
|
+
module Throwawaymail
|
|
10
|
+
CHANNEL = "throwawaymail"
|
|
11
|
+
API_BASE = "https://throwawaymail.app/api"
|
|
12
|
+
HEADERS = { "Accept" => "application/json" }.freeze
|
|
13
|
+
|
|
14
|
+
module_function
|
|
15
|
+
|
|
16
|
+
# @return [EmailInfo]
|
|
17
|
+
def generate_email
|
|
18
|
+
resp = Http.post("#{API_BASE}/mailboxes", headers: HEADERS, timeout: 15)
|
|
19
|
+
resp.raise_for_status
|
|
20
|
+
data = resp.json
|
|
21
|
+
raise "throwawaymail: 创建邮箱响应无效" unless data.is_a?(Hash)
|
|
22
|
+
|
|
23
|
+
mailbox_id = (data["mailbox_id"] || "").strip
|
|
24
|
+
email = (data["address"] || "").strip
|
|
25
|
+
raise "throwawaymail: 创建邮箱响应无效" if mailbox_id.empty? || email.empty? || !email.include?("@")
|
|
26
|
+
|
|
27
|
+
EmailInfo.new(channel: CHANNEL, email: email, token: mailbox_id,
|
|
28
|
+
expires_at: data["expires_at"], created_at: data["created_at"])
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# @param token [String] mailbox_id
|
|
32
|
+
# @param email [String]
|
|
33
|
+
# @return [Array<Email>]
|
|
34
|
+
def get_emails(token, email)
|
|
35
|
+
mailbox_id = (token || "").strip
|
|
36
|
+
address = (email || "").strip
|
|
37
|
+
raise "throwawaymail: mailbox_id 为空" if mailbox_id.empty?
|
|
38
|
+
raise "throwawaymail: 邮箱地址为空" if address.empty?
|
|
39
|
+
|
|
40
|
+
resp = Http.get("#{API_BASE}/mailboxes/#{URI.encode_www_form_component(mailbox_id)}/messages",
|
|
41
|
+
headers: HEADERS, timeout: 15)
|
|
42
|
+
resp.raise_for_status
|
|
43
|
+
rows = resp.json
|
|
44
|
+
return [] unless rows.is_a?(Array)
|
|
45
|
+
|
|
46
|
+
rows.filter_map do |item|
|
|
47
|
+
next unless item.is_a?(Hash)
|
|
48
|
+
|
|
49
|
+
message_id = (item["message_id"] || "").strip
|
|
50
|
+
detail = nil
|
|
51
|
+
unless message_id.empty?
|
|
52
|
+
begin
|
|
53
|
+
dr = Http.get("#{API_BASE}/mailboxes/#{URI.encode_www_form_component(mailbox_id)}/messages/#{URI.encode_www_form_component(message_id)}",
|
|
54
|
+
headers: HEADERS, timeout: 15)
|
|
55
|
+
detail = dr.json if dr.ok? && dr.json.is_a?(Hash)
|
|
56
|
+
rescue StandardError
|
|
57
|
+
# 忽略
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
src = detail || item
|
|
62
|
+
to_addresses = src["to_addresses"]
|
|
63
|
+
to_val = to_addresses.is_a?(Array) && !to_addresses.empty? ? to_addresses[0] : address
|
|
64
|
+
|
|
65
|
+
raw = {
|
|
66
|
+
"id" => src["message_id"],
|
|
67
|
+
"from_address" => src["from_address"],
|
|
68
|
+
"fromName" => src["from_name"],
|
|
69
|
+
"to" => to_val,
|
|
70
|
+
"subject" => src["subject"] || "",
|
|
71
|
+
"received_at" => src["received_at"],
|
|
72
|
+
"text" => src["text"] || "",
|
|
73
|
+
"html" => src["html"] || ""
|
|
74
|
+
}
|
|
75
|
+
Normalize.normalize_email(raw, address)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "uri"
|
|
4
|
+
|
|
5
|
+
module TempmailSdk
|
|
6
|
+
module Providers
|
|
7
|
+
# tmail.link 临时邮箱渠道(Django CSRF)
|
|
8
|
+
# GET / 获取首页 HTML,正则提取邮箱地址
|
|
9
|
+
# GET /inbox/{email}/ 获取 csrftoken cookie
|
|
10
|
+
# POST /inbox/{email}/ 获取邮件列表(form: format=json + csrfmiddlewaretoken)
|
|
11
|
+
module TmailLink
|
|
12
|
+
CHANNEL = "tmail-link"
|
|
13
|
+
BASE_URL = "https://tmail.link"
|
|
14
|
+
|
|
15
|
+
EMAIL_RE = /([a-zA-Z0-9._%+\-]+@tmail\.link)/
|
|
16
|
+
|
|
17
|
+
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"
|
|
18
|
+
|
|
19
|
+
module_function
|
|
20
|
+
|
|
21
|
+
def browser_headers
|
|
22
|
+
{
|
|
23
|
+
"User-Agent" => UA,
|
|
24
|
+
"Accept" => "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8",
|
|
25
|
+
"Accept-Language" => "zh-CN,zh;q=0.9,en;q=0.8"
|
|
26
|
+
}
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def post_headers(email, token)
|
|
30
|
+
{
|
|
31
|
+
"User-Agent" => UA,
|
|
32
|
+
"Accept" => "application/json, text/javascript, */*; q=0.01",
|
|
33
|
+
"Accept-Language" => "zh-CN,zh;q=0.9,en;q=0.8",
|
|
34
|
+
"X-Requested-With" => "XMLHttpRequest",
|
|
35
|
+
"Content-Type" => "application/x-www-form-urlencoded; charset=UTF-8",
|
|
36
|
+
"Origin" => BASE_URL,
|
|
37
|
+
"Referer" => "#{BASE_URL}/inbox/#{email}/",
|
|
38
|
+
"Cookie" => "csrftoken=#{token}"
|
|
39
|
+
}
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# @return [EmailInfo]
|
|
43
|
+
def generate_email
|
|
44
|
+
resp = Http.get("#{BASE_URL}/", headers: browser_headers)
|
|
45
|
+
resp.raise_for_status
|
|
46
|
+
|
|
47
|
+
m = resp.body.match(EMAIL_RE)
|
|
48
|
+
raise "tmail-link: 未能从首页提取邮箱地址" unless m
|
|
49
|
+
|
|
50
|
+
email = m[1].strip
|
|
51
|
+
raise "tmail-link: 提取的邮箱地址为空" if email.empty?
|
|
52
|
+
|
|
53
|
+
resp2 = Http.get("#{BASE_URL}/inbox/#{email}/", headers: browser_headers)
|
|
54
|
+
resp2.raise_for_status
|
|
55
|
+
|
|
56
|
+
token = resp2.cookie_value("csrftoken")
|
|
57
|
+
raise "tmail-link: 未能获取 csrftoken" if token.nil? || token.empty?
|
|
58
|
+
|
|
59
|
+
EmailInfo.new(channel: CHANNEL, email: email, token: token)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# @param email [String]
|
|
63
|
+
# @param token [String]
|
|
64
|
+
# @return [Array<Email>]
|
|
65
|
+
def get_emails(email, token)
|
|
66
|
+
addr = (email || "").strip
|
|
67
|
+
raise "tmail-link: 邮箱地址为空" if addr.empty?
|
|
68
|
+
|
|
69
|
+
csrf = (token || "").strip
|
|
70
|
+
raise "tmail-link: csrftoken 为空" if csrf.empty?
|
|
71
|
+
|
|
72
|
+
body = "format=json&csrfmiddlewaretoken=#{URI.encode_www_form_component(csrf)}"
|
|
73
|
+
resp = Http.post("#{BASE_URL}/inbox/#{addr}/",
|
|
74
|
+
headers: post_headers(addr, csrf), body: body)
|
|
75
|
+
resp.raise_for_status
|
|
76
|
+
|
|
77
|
+
data = resp.json
|
|
78
|
+
raise "tmail-link: 邮件列表响应格式异常" unless data.is_a?(Hash)
|
|
79
|
+
|
|
80
|
+
messages = data["messages"]
|
|
81
|
+
return [] unless messages.is_a?(Array) && !messages.empty?
|
|
82
|
+
|
|
83
|
+
messages.filter_map do |item|
|
|
84
|
+
next unless item.is_a?(Hash)
|
|
85
|
+
|
|
86
|
+
raw = {
|
|
87
|
+
"id" => (item["key"] || "").to_s,
|
|
88
|
+
"from" => (item["sender"] || "").to_s,
|
|
89
|
+
"to" => addr,
|
|
90
|
+
"subject" => (item["subject"] || "").to_s,
|
|
91
|
+
"date" => (item["date"] || "").to_s
|
|
92
|
+
}
|
|
93
|
+
Normalize.normalize_email(raw, addr)
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "uri"
|
|
4
|
+
|
|
5
|
+
module TempmailSdk
|
|
6
|
+
module Providers
|
|
7
|
+
# UnCorreoTemporal 渠道 — https://uncorreotemporal.com
|
|
8
|
+
# POST /api/v1/mailboxes 创建邮箱,GET /api/v1/mailboxes/{email}/messages 获取邮件
|
|
9
|
+
module Uncorreotemporal
|
|
10
|
+
CHANNEL = "uncorreotemporal"
|
|
11
|
+
API_BASE = "https://uncorreotemporal.com/api/v1"
|
|
12
|
+
HEADERS = {
|
|
13
|
+
"Accept" => "application/json",
|
|
14
|
+
"Origin" => "https://uncorreotemporal.com",
|
|
15
|
+
"Referer" => "https://uncorreotemporal.com/",
|
|
16
|
+
"User-Agent" => "Mozilla/5.0"
|
|
17
|
+
}.freeze
|
|
18
|
+
|
|
19
|
+
module_function
|
|
20
|
+
|
|
21
|
+
def flatten(raw, recipient)
|
|
22
|
+
{
|
|
23
|
+
"id" => raw["id"] || "",
|
|
24
|
+
"from" => raw["from_address"] || "",
|
|
25
|
+
"to" => raw["to_address"] || recipient,
|
|
26
|
+
"subject" => raw["subject"] || "",
|
|
27
|
+
"text" => raw["body_text"] || "",
|
|
28
|
+
"html" => raw["body_html"] || "",
|
|
29
|
+
"date" => raw["received_at"] || "",
|
|
30
|
+
"isRead" => !!raw["is_read"],
|
|
31
|
+
"attachments" => raw["attachments"].is_a?(Array) ? raw["attachments"] : []
|
|
32
|
+
}
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# @return [EmailInfo]
|
|
36
|
+
def generate_email
|
|
37
|
+
resp = Http.post("#{API_BASE}/mailboxes",
|
|
38
|
+
headers: HEADERS.merge("Content-Type" => "application/json"), timeout: 15)
|
|
39
|
+
resp.raise_for_status
|
|
40
|
+
data = resp.json
|
|
41
|
+
raise "uncorreotemporal: 创建邮箱响应无效" unless data.is_a?(Hash)
|
|
42
|
+
|
|
43
|
+
email = (data["address"] || "").strip
|
|
44
|
+
token = (data["session_token"] || "").strip
|
|
45
|
+
raise "uncorreotemporal: 创建邮箱响应无效" if email.empty? || !email.include?("@") || token.empty?
|
|
46
|
+
|
|
47
|
+
EmailInfo.new(channel: CHANNEL, email: email, token: token, expires_at: data["expires_at"])
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# @param token [String] session_token
|
|
51
|
+
# @param email [String]
|
|
52
|
+
# @return [Array<Email>]
|
|
53
|
+
def get_emails(token, email)
|
|
54
|
+
session_token = (token || "").strip
|
|
55
|
+
address = (email || "").strip
|
|
56
|
+
raise "uncorreotemporal: session_token 为空" if session_token.empty?
|
|
57
|
+
raise "uncorreotemporal: 邮箱地址为空" if address.empty?
|
|
58
|
+
|
|
59
|
+
resp = Http.get("#{API_BASE}/mailboxes/#{URI.encode_www_form_component(address)}/messages?limit=50",
|
|
60
|
+
headers: HEADERS.merge("X-Session-Token" => session_token), timeout: 15)
|
|
61
|
+
resp.raise_for_status
|
|
62
|
+
data = resp.json
|
|
63
|
+
rows = data.is_a?(Array) ? data : []
|
|
64
|
+
|
|
65
|
+
rows.filter_map do |row|
|
|
66
|
+
next unless row.is_a?(Hash)
|
|
67
|
+
|
|
68
|
+
message_id = (row["id"] || "").to_s.strip
|
|
69
|
+
detail = nil
|
|
70
|
+
unless message_id.empty?
|
|
71
|
+
begin
|
|
72
|
+
dr = Http.get("#{API_BASE}/mailboxes/#{URI.encode_www_form_component(address)}/messages/#{URI.encode_www_form_component(message_id)}",
|
|
73
|
+
headers: HEADERS.merge("X-Session-Token" => session_token), timeout: 15)
|
|
74
|
+
detail = dr.json if dr.ok? && dr.json.is_a?(Hash)
|
|
75
|
+
rescue StandardError
|
|
76
|
+
# 忽略
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
Normalize.normalize_email(flatten(detail || row, address), address)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "base64"
|
|
4
|
+
require "json"
|
|
5
|
+
require "uri"
|
|
6
|
+
|
|
7
|
+
module TempmailSdk
|
|
8
|
+
module Providers
|
|
9
|
+
# webmailtemp.com 临时邮箱渠道
|
|
10
|
+
# GET /api/create 创建邮箱
|
|
11
|
+
# GET /api/check/{username} 获取邮件列表
|
|
12
|
+
module Webmailtemp
|
|
13
|
+
CHANNEL = "webmailtemp"
|
|
14
|
+
BASE_URL = "https://webmailtemp.com"
|
|
15
|
+
TOK_PREFIX = "wmt1:"
|
|
16
|
+
|
|
17
|
+
HEADERS = {
|
|
18
|
+
"Accept" => "application/json",
|
|
19
|
+
"User-Agent" => "Mozilla/5.0"
|
|
20
|
+
}.freeze
|
|
21
|
+
|
|
22
|
+
module_function
|
|
23
|
+
|
|
24
|
+
def encode_token(username, cookie_hdr)
|
|
25
|
+
raw = JSON.generate({ "u" => username, "c" => cookie_hdr })
|
|
26
|
+
"#{TOK_PREFIX}#{Base64.strict_encode64(raw)}"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def decode_token(token)
|
|
30
|
+
raise "webmailtemp: 无效的 token" unless token&.start_with?(TOK_PREFIX)
|
|
31
|
+
|
|
32
|
+
data = JSON.parse(Base64.decode64(token[TOK_PREFIX.length..]))
|
|
33
|
+
username = (data["u"] || "").strip
|
|
34
|
+
cookie_hdr = (data["c"] || "").strip
|
|
35
|
+
raise "webmailtemp: token 数据无效" if username.empty? || cookie_hdr.empty?
|
|
36
|
+
|
|
37
|
+
{ "username" => username, "cookie" => cookie_hdr }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# @return [EmailInfo]
|
|
41
|
+
def generate_email
|
|
42
|
+
resp = Http.get("#{BASE_URL}/api/create", headers: HEADERS, timeout: 15)
|
|
43
|
+
resp.raise_for_status
|
|
44
|
+
data = resp.json
|
|
45
|
+
raise "webmailtemp: 创建邮箱响应无效" unless data.is_a?(Hash)
|
|
46
|
+
|
|
47
|
+
email = (data["email"] || "").strip
|
|
48
|
+
username = (data["username"] || "").strip
|
|
49
|
+
|
|
50
|
+
# 从 set_cookies 提取第一个 cookie
|
|
51
|
+
cookie_hdr = ""
|
|
52
|
+
cookies = resp.set_cookies
|
|
53
|
+
unless cookies.empty?
|
|
54
|
+
pair = cookies.first.split(";").first.to_s.strip
|
|
55
|
+
cookie_hdr = pair unless pair.empty?
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
unless data["success"] && !email.empty? && email.include?("@") && !username.empty? && !cookie_hdr.empty?
|
|
59
|
+
raise "webmailtemp: 创建邮箱响应无效"
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
expires_at = nil
|
|
63
|
+
ttl = data["ttl"]
|
|
64
|
+
if ttl.is_a?(Numeric) && ttl > 0
|
|
65
|
+
expires_at = ((Time.now.to_f + ttl) * 1000).to_i
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
EmailInfo.new(channel: CHANNEL, email: email, token: encode_token(username, cookie_hdr),
|
|
69
|
+
expires_at: expires_at)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# @param token [String]
|
|
73
|
+
# @param email [String]
|
|
74
|
+
# @return [Array<Email>]
|
|
75
|
+
def get_emails(token, email)
|
|
76
|
+
address = (email || "").strip
|
|
77
|
+
raise "webmailtemp: 邮箱地址为空" if address.empty?
|
|
78
|
+
|
|
79
|
+
session = decode_token((token || "").strip)
|
|
80
|
+
headers = HEADERS.merge("Cookie" => session["cookie"])
|
|
81
|
+
|
|
82
|
+
resp = Http.get("#{BASE_URL}/api/check/#{URI.encode_www_form_component(session["username"])}",
|
|
83
|
+
headers: headers, timeout: 15)
|
|
84
|
+
resp.raise_for_status
|
|
85
|
+
data = resp.json
|
|
86
|
+
|
|
87
|
+
rows = data.is_a?(Hash) ? data["emails"] : nil
|
|
88
|
+
return [] unless rows.is_a?(Array)
|
|
89
|
+
|
|
90
|
+
rows.filter_map do |row|
|
|
91
|
+
next unless row.is_a?(Hash)
|
|
92
|
+
|
|
93
|
+
raw = {
|
|
94
|
+
"id" => row["id"],
|
|
95
|
+
"from" => row["from"],
|
|
96
|
+
"to" => address,
|
|
97
|
+
"subject" => row["subject"],
|
|
98
|
+
"text" => row["body"],
|
|
99
|
+
"html" => row["html"],
|
|
100
|
+
"date" => row["received_at"] || row["timestamp"],
|
|
101
|
+
"attachments" => row["attachments"]
|
|
102
|
+
}
|
|
103
|
+
Normalize.normalize_email(raw, address)
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "uri"
|
|
4
|
+
|
|
5
|
+
module TempmailSdk
|
|
6
|
+
module Providers
|
|
7
|
+
# xkx.me 临时邮箱渠道(CSRF + session cookie)
|
|
8
|
+
# GET / 获取 CSRF token + session cookie
|
|
9
|
+
# POST /mailbox/create/random 创建邮箱(不跟随重定向,从 Location 提取邮箱)
|
|
10
|
+
# GET /mailbox/{email}/messages 获取邮件列表
|
|
11
|
+
module XkxMe
|
|
12
|
+
CHANNEL = "xkx-me"
|
|
13
|
+
BASE_URL = "https://xkx.me"
|
|
14
|
+
|
|
15
|
+
CSRF_RE = /csrf-token"\s+content="([^"]+)"/
|
|
16
|
+
|
|
17
|
+
HEADERS = {
|
|
18
|
+
"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"
|
|
19
|
+
}.freeze
|
|
20
|
+
|
|
21
|
+
module_function
|
|
22
|
+
|
|
23
|
+
def extract_cookie_header(resp)
|
|
24
|
+
cookies = {}
|
|
25
|
+
resp.set_cookies.each do |line|
|
|
26
|
+
pair = line.split(";").first.to_s.strip
|
|
27
|
+
k, v = pair.split("=", 2)
|
|
28
|
+
cookies[k] = v if k && v
|
|
29
|
+
end
|
|
30
|
+
cookies.sort.map { |k, v| "#{k}=#{v}" }.join("; ")
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# @return [EmailInfo]
|
|
34
|
+
def generate_email
|
|
35
|
+
# 获取 CSRF token 和 session cookie
|
|
36
|
+
resp = Http.get(BASE_URL, headers: HEADERS, timeout: 15)
|
|
37
|
+
resp.raise_for_status
|
|
38
|
+
|
|
39
|
+
m = resp.body.match(CSRF_RE)
|
|
40
|
+
raise "xkx-me: 无法获取 CSRF token" unless m
|
|
41
|
+
|
|
42
|
+
csrf = m[1]
|
|
43
|
+
cookie_hdr = extract_cookie_header(resp)
|
|
44
|
+
|
|
45
|
+
# POST 创建邮箱(不跟随重定向)
|
|
46
|
+
post_headers = HEADERS.merge(
|
|
47
|
+
"Content-Type" => "application/x-www-form-urlencoded",
|
|
48
|
+
"Referer" => "#{BASE_URL}/"
|
|
49
|
+
)
|
|
50
|
+
post_headers["Cookie"] = cookie_hdr unless cookie_hdr.empty?
|
|
51
|
+
|
|
52
|
+
body = "_token=#{URI.encode_www_form_component(csrf)}"
|
|
53
|
+
resp2 = Http.post("#{BASE_URL}/mailbox/create/random",
|
|
54
|
+
headers: post_headers, body: body, timeout: 15)
|
|
55
|
+
|
|
56
|
+
# 从 Location 头提取邮箱地址(302 重定向)
|
|
57
|
+
location = resp2.header("Location") || ""
|
|
58
|
+
email_match = location.match(/mailbox\/([^\/"'<>\s]+@xkx\.me)/)
|
|
59
|
+
raise "xkx-me: 无法从响应中提取邮箱地址" unless email_match
|
|
60
|
+
|
|
61
|
+
email = email_match[1]
|
|
62
|
+
|
|
63
|
+
EmailInfo.new(channel: CHANNEL, email: email, token: cookie_hdr)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# @param token [String]
|
|
67
|
+
# @param email [String]
|
|
68
|
+
# @return [Array<Email>]
|
|
69
|
+
def get_emails(token, email)
|
|
70
|
+
address = (email || "").strip
|
|
71
|
+
raise "xkx-me: 缺少邮箱地址" if address.empty?
|
|
72
|
+
|
|
73
|
+
cookie_str = (token || "").strip
|
|
74
|
+
|
|
75
|
+
headers = HEADERS.merge(
|
|
76
|
+
"Accept" => "application/json",
|
|
77
|
+
"X-Requested-With" => "XMLHttpRequest"
|
|
78
|
+
)
|
|
79
|
+
headers["Cookie"] = cookie_str unless cookie_str.empty?
|
|
80
|
+
|
|
81
|
+
encoded_email = URI.encode_www_form_component(address).gsub("%40", "@")
|
|
82
|
+
resp = Http.get("#{BASE_URL}/mailbox/#{encoded_email}/messages",
|
|
83
|
+
headers: headers, timeout: 15)
|
|
84
|
+
return [] if resp.status_code == 404
|
|
85
|
+
|
|
86
|
+
resp.raise_for_status
|
|
87
|
+
data = resp.json
|
|
88
|
+
return [] unless data
|
|
89
|
+
|
|
90
|
+
message_list = if data.is_a?(Array)
|
|
91
|
+
data
|
|
92
|
+
elsif data.is_a?(Hash)
|
|
93
|
+
if data.key?("messages")
|
|
94
|
+
data["messages"]
|
|
95
|
+
elsif data["message"].is_a?(Hash)
|
|
96
|
+
[data["message"]]
|
|
97
|
+
else
|
|
98
|
+
[]
|
|
99
|
+
end
|
|
100
|
+
else
|
|
101
|
+
[]
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
return [] unless message_list.is_a?(Array)
|
|
105
|
+
|
|
106
|
+
message_list.filter_map do |msg|
|
|
107
|
+
next unless msg.is_a?(Hash)
|
|
108
|
+
|
|
109
|
+
raw = {
|
|
110
|
+
"id" => (msg["id"] || "").to_s,
|
|
111
|
+
"from" => (msg["from"] || "").to_s,
|
|
112
|
+
"to" => address,
|
|
113
|
+
"subject" => (msg["subject"] || "").to_s,
|
|
114
|
+
"date" => (msg["date"] || "").to_s,
|
|
115
|
+
"html" => (msg["html"] || msg["body"] || "").to_s,
|
|
116
|
+
"text" => (msg["text"] || "").to_s,
|
|
117
|
+
"is_read" => false,
|
|
118
|
+
"attachments" => []
|
|
119
|
+
}
|
|
120
|
+
Normalize.normalize_email(raw, address)
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|