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.
Files changed (100) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +116 -0
  3. data/lib/tempmail_sdk/channels.rb +289 -0
  4. data/lib/tempmail_sdk/channels_data.rb +289 -0
  5. data/lib/tempmail_sdk/client.rb +193 -0
  6. data/lib/tempmail_sdk/config.rb +104 -0
  7. data/lib/tempmail_sdk/html_utils.rb +42 -0
  8. data/lib/tempmail_sdk/http.rb +225 -0
  9. data/lib/tempmail_sdk/logger.rb +38 -0
  10. data/lib/tempmail_sdk/normalize.rb +193 -0
  11. data/lib/tempmail_sdk/providers/anonbox.rb +248 -0
  12. data/lib/tempmail_sdk/providers/anonymmail.rb +92 -0
  13. data/lib/tempmail_sdk/providers/byom.rb +48 -0
  14. data/lib/tempmail_sdk/providers/catchmail.rb +110 -0
  15. data/lib/tempmail_sdk/providers/cleantempmail.rb +49 -0
  16. data/lib/tempmail_sdk/providers/devmail_uk.rb +58 -0
  17. data/lib/tempmail_sdk/providers/dropmail.rb +133 -0
  18. data/lib/tempmail_sdk/providers/dropmail_click.rb +81 -0
  19. data/lib/tempmail_sdk/providers/dropmail_me.rb +163 -0
  20. data/lib/tempmail_sdk/providers/duckmail.rb +63 -0
  21. data/lib/tempmail_sdk/providers/eyepaste.rb +119 -0
  22. data/lib/tempmail_sdk/providers/fake_email_site.rb +48 -0
  23. data/lib/tempmail_sdk/providers/fake_legal.rb +109 -0
  24. data/lib/tempmail_sdk/providers/fmail.rb +87 -0
  25. data/lib/tempmail_sdk/providers/freecustom.rb +135 -0
  26. data/lib/tempmail_sdk/providers/getnada.rb +146 -0
  27. data/lib/tempmail_sdk/providers/gonebox_email.rb +89 -0
  28. data/lib/tempmail_sdk/providers/guerrillamail.rb +62 -0
  29. data/lib/tempmail_sdk/providers/guerrillamail_mirrors.rb +81 -0
  30. data/lib/tempmail_sdk/providers/harakirimail.rb +84 -0
  31. data/lib/tempmail_sdk/providers/haribu.rb +176 -0
  32. data/lib/tempmail_sdk/providers/inboxes.rb +110 -0
  33. data/lib/tempmail_sdk/providers/inboxkitten.rb +90 -0
  34. data/lib/tempmail_sdk/providers/linshiyou.rb +157 -0
  35. data/lib/tempmail_sdk/providers/lroid.rb +170 -0
  36. data/lib/tempmail_sdk/providers/m2u.rb +126 -0
  37. data/lib/tempmail_sdk/providers/mail10s.rb +59 -0
  38. data/lib/tempmail_sdk/providers/mail123.rb +82 -0
  39. data/lib/tempmail_sdk/providers/mail_cx.rb +133 -0
  40. data/lib/tempmail_sdk/providers/mail_sunls.rb +68 -0
  41. data/lib/tempmail_sdk/providers/mail_tm.rb +99 -0
  42. data/lib/tempmail_sdk/providers/mailcatch.rb +91 -0
  43. data/lib/tempmail_sdk/providers/maildrop.rb +92 -0
  44. data/lib/tempmail_sdk/providers/maildrop_cc.rb +110 -0
  45. data/lib/tempmail_sdk/providers/mailforspam.rb +96 -0
  46. data/lib/tempmail_sdk/providers/mailhole_de.rb +70 -0
  47. data/lib/tempmail_sdk/providers/mailinator.rb +182 -0
  48. data/lib/tempmail_sdk/providers/mailmomy.rb +108 -0
  49. data/lib/tempmail_sdk/providers/mailnesia.rb +141 -0
  50. data/lib/tempmail_sdk/providers/mffac.rb +101 -0
  51. data/lib/tempmail_sdk/providers/moakt.rb +296 -0
  52. data/lib/tempmail_sdk/providers/neighbours.rb +140 -0
  53. data/lib/tempmail_sdk/providers/neighbours_sh.rb +117 -0
  54. data/lib/tempmail_sdk/providers/nimail.rb +100 -0
  55. data/lib/tempmail_sdk/providers/ockito.rb +163 -0
  56. data/lib/tempmail_sdk/providers/one_sec_mail.rb +46 -0
  57. data/lib/tempmail_sdk/providers/restmail_net.rb +60 -0
  58. data/lib/tempmail_sdk/providers/rootsh.rb +167 -0
  59. data/lib/tempmail_sdk/providers/shitty_email.rb +84 -0
  60. data/lib/tempmail_sdk/providers/smailpro.rb +103 -0
  61. data/lib/tempmail_sdk/providers/ta_easy.rb +53 -0
  62. data/lib/tempmail_sdk/providers/temp_mail_io.rb +67 -0
  63. data/lib/tempmail_sdk/providers/temp_mail_now.rb +103 -0
  64. data/lib/tempmail_sdk/providers/temp_mail_org.rb +89 -0
  65. data/lib/tempmail_sdk/providers/tempemail_co.rb +142 -0
  66. data/lib/tempmail_sdk/providers/tempemail_info.rb +121 -0
  67. data/lib/tempmail_sdk/providers/tempemails_net.rb +159 -0
  68. data/lib/tempmail_sdk/providers/tempfastmail.rb +76 -0
  69. data/lib/tempmail_sdk/providers/tempgmailer.rb +126 -0
  70. data/lib/tempmail_sdk/providers/tempgo_email.rb +74 -0
  71. data/lib/tempmail_sdk/providers/tempinbox.rb +70 -0
  72. data/lib/tempmail_sdk/providers/tempmail.rb +66 -0
  73. data/lib/tempmail_sdk/providers/tempmail365.rb +119 -0
  74. data/lib/tempmail_sdk/providers/tempmail_fish.rb +89 -0
  75. data/lib/tempmail_sdk/providers/tempmail_fyi.rb +124 -0
  76. data/lib/tempmail_sdk/providers/tempmail_lol.rb +42 -0
  77. data/lib/tempmail_sdk/providers/tempmail_lol_v2.rb +57 -0
  78. data/lib/tempmail_sdk/providers/tempmail_plus.rb +80 -0
  79. data/lib/tempmail_sdk/providers/tempmailc.rb +80 -0
  80. data/lib/tempmail_sdk/providers/tempmailpro.rb +89 -0
  81. data/lib/tempmail_sdk/providers/tempmailten.rb +150 -0
  82. data/lib/tempmail_sdk/providers/temporam.rb +95 -0
  83. data/lib/tempmail_sdk/providers/tempp_mails.rb +150 -0
  84. data/lib/tempmail_sdk/providers/tempy_email.rb +73 -0
  85. data/lib/tempmail_sdk/providers/ten_minute_mail_net.rb +125 -0
  86. data/lib/tempmail_sdk/providers/tenminute_one.rb +255 -0
  87. data/lib/tempmail_sdk/providers/throwawaymail.rb +80 -0
  88. data/lib/tempmail_sdk/providers/tmail_link.rb +98 -0
  89. data/lib/tempmail_sdk/providers/uncorreotemporal.rb +84 -0
  90. data/lib/tempmail_sdk/providers/webmailtemp.rb +108 -0
  91. data/lib/tempmail_sdk/providers/xkx_me.rb +125 -0
  92. data/lib/tempmail_sdk/providers/zhujump.rb +256 -0
  93. data/lib/tempmail_sdk/registry.rb +1097 -0
  94. data/lib/tempmail_sdk/retry.rb +91 -0
  95. data/lib/tempmail_sdk/telemetry.rb +134 -0
  96. data/lib/tempmail_sdk/types.rb +164 -0
  97. data/lib/tempmail_sdk/version.rb +6 -0
  98. data/lib/tempmail_sdk/webui.rb +360 -0
  99. data/lib/tempmail_sdk.rb +76 -0
  100. metadata +142 -0
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "base64"
4
+ require "json"
5
+ require "uri"
6
+
7
+ module TempmailSdk
8
+ module Providers
9
+ # temp-mail.fyi 临时邮箱渠道
10
+ # GET / 获取 PHPSESSID cookie + CSRF token
11
+ # POST /api/generate_email.php 创建邮箱
12
+ # POST /api/get_emails.php 获取邮件列表
13
+ module TempmailFyi
14
+ CHANNEL = "tempmail-fyi"
15
+ BASE_URL = "https://temp-mail.fyi"
16
+ TOK_PREFIX = "tmf1:"
17
+
18
+ CSRF_RE = /csrfToken"\s*value="([^"]+)"/
19
+
20
+ 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"
21
+
22
+ module_function
23
+
24
+ def browser_headers
25
+ {
26
+ "User-Agent" => UA,
27
+ "Accept" => "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8",
28
+ "Accept-Language" => "en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7"
29
+ }
30
+ end
31
+
32
+ def api_headers(csrf, cookie_hdr)
33
+ h = {
34
+ "User-Agent" => UA,
35
+ "Accept" => "application/json, text/javascript, */*; q=0.01",
36
+ "Accept-Language" => "en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7",
37
+ "Content-Type" => "application/json",
38
+ "X-CSRF-Token" => csrf,
39
+ "X-Requested-With" => "XMLHttpRequest",
40
+ "Referer" => "#{BASE_URL}/"
41
+ }
42
+ h["Cookie"] = cookie_hdr unless cookie_hdr.empty?
43
+ h
44
+ end
45
+
46
+ def extract_cookie_header(resp)
47
+ cookies = {}
48
+ resp.set_cookies.each do |line|
49
+ pair = line.split(";").first.to_s.strip
50
+ k, v = pair.split("=", 2)
51
+ cookies[k] = v if k && v
52
+ end
53
+ cookies.sort.map { |k, v| "#{k}=#{v}" }.join("; ")
54
+ end
55
+
56
+ def encode_token(csrf, cookie_hdr)
57
+ raw = JSON.generate({ "t" => csrf, "c" => cookie_hdr })
58
+ "#{TOK_PREFIX}#{Base64.strict_encode64(raw)}"
59
+ end
60
+
61
+ def decode_token(token)
62
+ raise "tempmail-fyi: 无效的 token" unless token&.start_with?(TOK_PREFIX)
63
+
64
+ data = JSON.parse(Base64.decode64(token[TOK_PREFIX.length..]))
65
+ csrf = (data["t"] || "").strip
66
+ cookie_hdr = (data["c"] || "").strip
67
+ raise "tempmail-fyi: token 中缺少 CSRF" if csrf.empty?
68
+
69
+ [csrf, cookie_hdr]
70
+ end
71
+
72
+ # @return [EmailInfo]
73
+ def generate_email
74
+ resp = Http.get("#{BASE_URL}/", headers: browser_headers)
75
+ resp.raise_for_status
76
+
77
+ cookie_hdr = extract_cookie_header(resp)
78
+ m = resp.body.match(CSRF_RE)
79
+ raise "tempmail-fyi: 未能从首页提取 CSRF token" unless m
80
+
81
+ csrf = m[1]
82
+
83
+ resp2 = Http.post("#{BASE_URL}/api/generate_email.php",
84
+ headers: api_headers(csrf, cookie_hdr), body: "{}")
85
+ resp2.raise_for_status
86
+ data = resp2.json
87
+ raise "tempmail-fyi: 创建邮箱响应格式异常" unless data.is_a?(Hash)
88
+ raise "tempmail-fyi: 创建邮箱失败" unless data["success"]
89
+
90
+ email = (data["email_address"] || "").strip
91
+ raise "tempmail-fyi: 获取到的邮箱地址无效" if email.empty? || !email.include?("@")
92
+
93
+ EmailInfo.new(channel: CHANNEL, email: email, token: encode_token(csrf, cookie_hdr),
94
+ expires_at: data["expires_at"])
95
+ end
96
+
97
+ # @param email [String]
98
+ # @param token [String]
99
+ # @return [Array<Email>]
100
+ def get_emails(email, token)
101
+ addr = (email || "").strip
102
+ raise "tempmail-fyi: 邮箱地址为空" if addr.empty?
103
+
104
+ csrf, cookie_hdr = decode_token((token || "").strip)
105
+
106
+ resp = Http.post("#{BASE_URL}/api/get_emails.php",
107
+ headers: api_headers(csrf, cookie_hdr),
108
+ body: JSON.generate({ "email_address" => addr }))
109
+ resp.raise_for_status
110
+ data = resp.json
111
+ raise "tempmail-fyi: 邮件列表响应格式异常" unless data.is_a?(Hash)
112
+
113
+ emails = data["emails"]
114
+ return [] unless emails.is_a?(Array) && !emails.empty?
115
+
116
+ emails.filter_map do |item|
117
+ next unless item.is_a?(Hash)
118
+
119
+ Normalize.normalize_email(item, addr)
120
+ end
121
+ end
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TempmailSdk
4
+ module Providers
5
+ # tempmail.lol 渠道实现
6
+ # API: https://api.tempmail.lol/v2
7
+ module TempmailLol
8
+ CHANNEL = "tempmail-lol"
9
+ BASE_URL = "https://api.tempmail.lol/v2"
10
+ DEFAULT_HEADERS = {
11
+ "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",
12
+ "Content-Type" => "application/json",
13
+ "Origin" => "https://tempmail.lol",
14
+ "DNT" => "1"
15
+ }.freeze
16
+
17
+ module_function
18
+
19
+ # 创建临时邮箱,支持指定域名
20
+ # @param domain [String, nil]
21
+ # @return [EmailInfo]
22
+ def generate_email(domain = nil)
23
+ resp = Http.post("#{BASE_URL}/inbox/create", headers: DEFAULT_HEADERS,
24
+ json: { "domain" => domain, "captcha" => nil })
25
+ resp.raise_for_status
26
+ data = resp.json
27
+ raise "Failed to generate email" if data["address"].to_s.empty? || data["token"].to_s.empty?
28
+
29
+ EmailInfo.new(channel: CHANNEL, email: data["address"], token: data["token"])
30
+ end
31
+
32
+ # @param email [String]
33
+ # @param token [String]
34
+ # @return [Array<Email>]
35
+ def get_emails(email, token)
36
+ resp = Http.get("#{BASE_URL}/inbox?token=#{URI.encode_www_form_component(token)}", headers: DEFAULT_HEADERS)
37
+ resp.raise_for_status
38
+ (resp.json["emails"] || []).map { |raw| Normalize.normalize_email(raw, email) }
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "uri"
4
+
5
+ module TempmailSdk
6
+ module Providers
7
+ # tempmail.lol V2 渠道 — https://api.tempmail.lol
8
+ # GET /generate 创建邮箱,GET /auth/{token} 获取邮件
9
+ module TempmailLolV2
10
+ CHANNEL = "tempmail-lol-v2"
11
+ API_BASE = "https://api.tempmail.lol"
12
+ HEADERS = {
13
+ "Accept" => "application/json",
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
+ }.freeze
16
+
17
+ module_function
18
+
19
+ # @return [EmailInfo]
20
+ def generate_email
21
+ resp = Http.get("#{API_BASE}/generate", headers: HEADERS)
22
+ resp.raise_for_status
23
+ data = resp.json
24
+ raise "tempmail-lol-v2: 缺少 address 或 token" if (data["address"] || "").empty? || (data["token"] || "").empty?
25
+
26
+ EmailInfo.new(channel: CHANNEL, email: data["address"], token: data["token"])
27
+ end
28
+
29
+ # @param token [String]
30
+ # @param email [String]
31
+ # @return [Array<Email>]
32
+ def get_emails(token, email)
33
+ resp = Http.get("#{API_BASE}/auth/#{URI.encode_www_form_component(token)}", headers: HEADERS)
34
+ resp.raise_for_status
35
+ data = resp.json
36
+
37
+ mail_list = data["email"] || []
38
+ return [] unless mail_list.is_a?(Array)
39
+
40
+ mail_list.filter_map do |raw|
41
+ next unless raw.is_a?(Hash)
42
+
43
+ normalized = {
44
+ "id" => raw["id"] || raw["_id"] || "",
45
+ "from" => raw["from"] || raw["sender"] || "",
46
+ "to" => email,
47
+ "subject" => raw["subject"] || "",
48
+ "text" => raw["body"] || raw["text"] || "",
49
+ "html" => raw["html"] || raw["body"] || "",
50
+ "date" => raw["date"] || raw["receivedAt"] || ""
51
+ }
52
+ Normalize.normalize_email(normalized, email)
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TempmailSdk
4
+ module Providers
5
+ # TempMail Plus 渠道 — https://tempmail.plus
6
+ # 无需认证的简单 REST API,默认域名 mailto.plus
7
+ module TempmailPlus
8
+ CHANNEL = "tempmail-plus"
9
+ API_BASE = "https://tempmail.plus/api/mails"
10
+ DOMAIN = "mailto.plus"
11
+ HEADERS = {
12
+ "Accept" => "application/json",
13
+ "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",
14
+ "Referer" => "https://tempmail.plus/",
15
+ "Origin" => "https://tempmail.plus"
16
+ }.freeze
17
+
18
+ module_function
19
+
20
+ def random_local(length = 12)
21
+ chars = ("a".."z").to_a + ("0".."9").to_a
22
+ Array.new(length) { chars.sample }.join
23
+ end
24
+
25
+ # 创建 tempmail-plus 临时邮箱
26
+ # @param domain [String]
27
+ # @param channel [String]
28
+ # @return [EmailInfo]
29
+ def generate_email(domain = DOMAIN, channel = CHANNEL)
30
+ selected_domain = (domain || DOMAIN).to_s.strip
31
+ selected_domain = DOMAIN if selected_domain.empty?
32
+ selected_channel = (channel || CHANNEL).to_s.strip
33
+ selected_channel = CHANNEL if selected_channel.empty?
34
+ email = "#{random_local}@#{selected_domain}"
35
+ # 调用列表接口验证地址可用
36
+ Http.get("#{API_BASE}/?email=#{email}&epin=", headers: HEADERS, timeout: 15).raise_for_status
37
+ EmailInfo.new(channel: selected_channel, email: email)
38
+ end
39
+
40
+ def fetch_body(mail_id, email)
41
+ return {} unless mail_id && mail_id != 0
42
+
43
+ resp = Http.get("#{API_BASE}/#{mail_id}?email=#{email}&epin=", headers: HEADERS, timeout: 15)
44
+ resp.raise_for_status
45
+ data = resp.json
46
+ data.is_a?(Hash) ? data : {}
47
+ rescue StandardError
48
+ {}
49
+ end
50
+
51
+ # @param email [String]
52
+ # @return [Array<Email>]
53
+ def get_emails(email)
54
+ e = email.to_s.strip
55
+ raise "tempmail-plus: 邮箱地址为空" if e.empty?
56
+
57
+ resp = Http.get("#{API_BASE}/?email=#{e}&epin=", headers: HEADERS, timeout: 15)
58
+ resp.raise_for_status
59
+ data = resp.json
60
+ return [] unless data.is_a?(Hash)
61
+
62
+ rows = data["mail_list"]
63
+ return [] unless rows.is_a?(Array)
64
+
65
+ rows.select { |r| r.is_a?(Hash) }.map do |raw|
66
+ mail_id = raw["mail_id"] || 0
67
+ detail = fetch_body(mail_id, e)
68
+ flat = {
69
+ "id" => mail_id != 0 ? mail_id.to_s : "",
70
+ "from" => raw["from_mail"] || raw["from_name"] || "",
71
+ "to" => e, "subject" => raw["subject"] || "", "date" => raw["time"] || "",
72
+ "html" => detail["html"] || "", "text" => detail["text"] || "",
73
+ "isRead" => !(raw["is_new"].nil? ? true : raw["is_new"])
74
+ }
75
+ Normalize.normalize_email(flat, e)
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TempmailSdk
4
+ module Providers
5
+ # TempMailC 渠道实现
6
+ # API: https://tempmailc.com/api/v1
7
+ module Tempmailc
8
+ CHANNEL = "tempmailc"
9
+ API_BASE = "https://tempmailc.com/api/v1"
10
+ HEADERS = { "Accept" => "application/json" }.freeze
11
+
12
+ module_function
13
+
14
+ # 扁平化列表消息(无正文时使用)
15
+ def flatten_list_message(raw, recipient)
16
+ {
17
+ "id" => raw["id"],
18
+ "from" => raw["from"],
19
+ "to" => recipient,
20
+ "subject" => raw["subject"] || "",
21
+ "timestamp" => raw["ts"],
22
+ "read" => raw["read"]
23
+ }
24
+ end
25
+
26
+ # 获取单封邮件详情
27
+ def fetch_message(email, message_id)
28
+ resp = Http.get(
29
+ "#{API_BASE}/message?email=#{URI.encode_www_form_component(email)}&msg_id=#{URI.encode_www_form_component(message_id)}",
30
+ headers: HEADERS, timeout: 15
31
+ )
32
+ return nil unless resp.ok?
33
+
34
+ data = resp.json
35
+ return nil unless data.is_a?(Hash) && data["ok"] != false
36
+
37
+ data
38
+ rescue StandardError
39
+ nil
40
+ end
41
+
42
+ # 创建临时邮箱
43
+ # @return [EmailInfo]
44
+ def generate_email
45
+ resp = Http.get("#{API_BASE}/new", headers: HEADERS, timeout: 15)
46
+ resp.raise_for_status
47
+ data = resp.json
48
+ email = data.is_a?(Hash) ? data["email"].to_s.strip : ""
49
+ raise "tempmailc: 无效的响应" unless data.is_a?(Hash) && data["ok"] && !email.empty? && email.include?("@")
50
+
51
+ EmailInfo.new(channel: CHANNEL, email: email)
52
+ end
53
+
54
+ # 获取邮件列表
55
+ # @param email [String]
56
+ # @return [Array<Email>]
57
+ def get_emails(email)
58
+ address = email.to_s.strip
59
+ raise "tempmailc: 邮箱地址为空" if address.empty?
60
+
61
+ resp = Http.get(
62
+ "#{API_BASE}/inbox?email=#{URI.encode_www_form_component(address)}",
63
+ headers: HEADERS, timeout: 15
64
+ )
65
+ resp.raise_for_status
66
+ data = resp.json
67
+ raise "tempmailc: inbox 响应失败" unless data.is_a?(Hash) && data["ok"]
68
+
69
+ rows = data["messages"]
70
+ return [] unless rows.is_a?(Array)
71
+
72
+ rows.select { |item| item.is_a?(Hash) }.map do |item|
73
+ mid = item["id"].to_s.strip
74
+ detail = mid.empty? ? nil : fetch_message(address, mid)
75
+ Normalize.normalize_email(detail || flatten_list_message(item, address), address)
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "uri"
4
+
5
+ module TempmailSdk
6
+ module Providers
7
+ # TempMail Pro 渠道 — https://tempmailpro.us
8
+ # POST /api/v1/mailbox/create 创建邮箱,GET /api/v1/mailbox/{token}/emails 获取邮件
9
+ module Tempmailpro
10
+ CHANNEL = "tempmailpro"
11
+ API_BASE = "https://tempmailpro.us/api/v1"
12
+ HEADERS = { "Accept" => "application/json", "User-Agent" => "Mozilla/5.0" }.freeze
13
+
14
+ module_function
15
+
16
+ def flatten(raw, recipient)
17
+ {
18
+ "id" => raw["id"] || raw["message_id"],
19
+ "from" => raw["from_address"] || raw["from_name"],
20
+ "to" => recipient,
21
+ "subject" => raw["subject"],
22
+ "text" => raw["body_text"],
23
+ "html" => raw["body_html"],
24
+ "date" => raw["received_at"],
25
+ "attachments" => raw["attachments"]
26
+ }
27
+ end
28
+
29
+ # @return [EmailInfo]
30
+ def generate_email
31
+ resp = Http.post("#{API_BASE}/mailbox/create",
32
+ headers: HEADERS.merge("Content-Type" => "application/json"),
33
+ json: {}, timeout: 15)
34
+ resp.raise_for_status
35
+ data = resp.json
36
+ raise "tempmailpro: 创建邮箱响应无效" unless data.is_a?(Hash)
37
+
38
+ box = data["data"] || {}
39
+ raise "tempmailpro: 创建邮箱响应无效" unless box.is_a?(Hash)
40
+
41
+ email = (box["address"] || "").strip
42
+ token = (box["token"] || "").strip
43
+ raise "tempmailpro: 创建邮箱响应无效" if email.empty? || !email.include?("@") || token.empty?
44
+
45
+ EmailInfo.new(channel: CHANNEL, email: email, token: token,
46
+ expires_at: box["expires_at"], created_at: box["created_at"])
47
+ end
48
+
49
+ # @param token [String]
50
+ # @param email [String]
51
+ # @return [Array<Email>]
52
+ def get_emails(token, email)
53
+ mailbox_token = (token || "").strip
54
+ address = (email || "").strip
55
+ raise "tempmailpro: token 为空" if mailbox_token.empty?
56
+ raise "tempmailpro: 邮箱地址为空" if address.empty?
57
+
58
+ resp = Http.get("#{API_BASE}/mailbox/#{URI.encode_www_form_component(mailbox_token)}/emails",
59
+ headers: HEADERS, timeout: 15)
60
+ resp.raise_for_status
61
+ data = resp.json
62
+ return [] unless data.is_a?(Hash)
63
+
64
+ rows = data["data"]
65
+ return [] unless rows.is_a?(Array)
66
+
67
+ rows.filter_map do |item|
68
+ next unless item.is_a?(Hash)
69
+
70
+ message_id = (item["id"] || "").to_s.strip
71
+ detail = nil
72
+ unless message_id.empty?
73
+ begin
74
+ dr = Http.get("#{API_BASE}/mailbox/#{URI.encode_www_form_component(mailbox_token)}/emails/#{URI.encode_www_form_component(message_id)}",
75
+ headers: HEADERS, timeout: 15)
76
+ if dr.ok?
77
+ dd = dr.json
78
+ detail = dd["data"] if dd.is_a?(Hash) && dd["data"].is_a?(Hash)
79
+ end
80
+ rescue StandardError
81
+ # 忽略
82
+ end
83
+ end
84
+ Normalize.normalize_email(flatten(detail || item, address), address)
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,150 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "base64"
4
+ require "json"
5
+ require "uri"
6
+
7
+ module TempmailSdk
8
+ module Providers
9
+ # tempmailten.com 临时邮箱渠道(Laravel + CSRF)
10
+ # GET /en 获取 session cookie + CSRF token
11
+ # POST /messages 获取邮箱地址和邮件列表
12
+ # GET /view/{id} 获取单封邮件 HTML 正文
13
+ module Tempmailten
14
+ CHANNEL = "tempmailten"
15
+ BASE_URL = "https://tempmailten.com"
16
+ TOK_PREFIX = "tmt1:"
17
+
18
+ CSRF_RE = /<meta\s+name="csrf-token"\s+content="([^"]+)"/
19
+
20
+ 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"
21
+
22
+ module_function
23
+
24
+ def browser_headers
25
+ {
26
+ "User-Agent" => UA,
27
+ "Accept" => "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8",
28
+ "Accept-Language" => "en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7"
29
+ }
30
+ end
31
+
32
+ def ajax_headers
33
+ {
34
+ "User-Agent" => UA,
35
+ "Accept" => "application/json, text/javascript, */*; q=0.01",
36
+ "Accept-Language" => "en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7",
37
+ "X-Requested-With" => "XMLHttpRequest",
38
+ "Referer" => "#{BASE_URL}/en"
39
+ }
40
+ end
41
+
42
+ def extract_cookie_header(resp)
43
+ cookies = {}
44
+ resp.set_cookies.each do |line|
45
+ pair = line.split(";").first.to_s.strip
46
+ k, v = pair.split("=", 2)
47
+ cookies[k] = v if k && v
48
+ end
49
+ cookies.sort.map { |k, v| "#{k}=#{v}" }.join("; ")
50
+ end
51
+
52
+ def encode_token(csrf, cookie_hdr)
53
+ raw = JSON.generate({ "t" => csrf, "c" => cookie_hdr })
54
+ "#{TOK_PREFIX}#{Base64.strict_encode64(raw)}"
55
+ end
56
+
57
+ def decode_token(token)
58
+ raise "tempmailten: 无效的 token" unless token&.start_with?(TOK_PREFIX)
59
+
60
+ data = JSON.parse(Base64.decode64(token[TOK_PREFIX.length..]))
61
+ csrf = (data["t"] || "").strip
62
+ cookie_hdr = (data["c"] || "").strip
63
+ raise "tempmailten: token 中缺少 CSRF" if csrf.empty?
64
+
65
+ [csrf, cookie_hdr]
66
+ end
67
+
68
+ def post_messages(csrf, cookie_hdr)
69
+ headers = ajax_headers.merge(
70
+ "Content-Type" => "application/x-www-form-urlencoded",
71
+ "X-CSRF-TOKEN" => csrf
72
+ )
73
+ headers["Cookie"] = cookie_hdr unless cookie_hdr.empty?
74
+
75
+ resp = Http.post("#{BASE_URL}/messages", headers: headers,
76
+ body: "_token=#{URI.encode_www_form_component(csrf)}&captcha=")
77
+ resp.raise_for_status
78
+ data = resp.json
79
+ raise "tempmailten: 解析响应 JSON 失败" unless data.is_a?(Hash)
80
+
81
+ data
82
+ end
83
+
84
+ # @return [EmailInfo]
85
+ def generate_email
86
+ resp = Http.get("#{BASE_URL}/en", headers: browser_headers)
87
+ resp.raise_for_status
88
+
89
+ cookie_hdr = extract_cookie_header(resp)
90
+ m = resp.body.match(CSRF_RE)
91
+ raise "tempmailten: 未能从首页提取 CSRF token" unless m
92
+
93
+ csrf = m[1]
94
+ data = post_messages(csrf, cookie_hdr)
95
+ mailbox = (data["mailbox"] || "").strip
96
+ raise "tempmailten: 邮箱地址无效" if mailbox.empty? || !mailbox.include?("@")
97
+
98
+ EmailInfo.new(channel: CHANNEL, email: mailbox, token: encode_token(csrf, cookie_hdr))
99
+ end
100
+
101
+ # @param email [String]
102
+ # @param token [String]
103
+ # @return [Array<Email>]
104
+ def get_emails(email, token)
105
+ addr = (email || "").strip
106
+ raise "tempmailten: 邮箱地址为空" if addr.empty?
107
+
108
+ csrf, cookie_hdr = decode_token((token || "").strip)
109
+ data = post_messages(csrf, cookie_hdr)
110
+
111
+ msgs = data["messages"]
112
+ return [] unless msgs.is_a?(Array) && !msgs.empty?
113
+
114
+ msgs.filter_map do |msg|
115
+ next unless msg.is_a?(Hash)
116
+
117
+ mid = (msg["id"] || "").to_s.strip
118
+ next if mid.empty?
119
+
120
+ from_addr = (msg["from_email"] || "").to_s
121
+ from_name = (msg["from"] || "").to_s
122
+ from_addr = "#{from_name} <#{from_addr}>" if !from_name.empty? && from_name != from_addr
123
+
124
+ raw = {
125
+ "id" => mid,
126
+ "from" => from_addr,
127
+ "to" => addr,
128
+ "subject" => msg["subject"] || "",
129
+ "html" => fetch_view(cookie_hdr, mid),
130
+ "isRead" => !!msg["is_seen"]
131
+ }
132
+ Normalize.normalize_email(raw, addr)
133
+ end
134
+ end
135
+
136
+ def fetch_view(cookie_hdr, mid)
137
+ return "" if mid.nil? || mid.empty?
138
+
139
+ headers = browser_headers.merge("Referer" => "#{BASE_URL}/en")
140
+ headers["Cookie"] = cookie_hdr unless cookie_hdr.empty?
141
+ resp = Http.get("#{BASE_URL}/view/#{URI.encode_www_form_component(mid)}", headers: headers)
142
+ return "" unless resp.ok?
143
+
144
+ resp.body
145
+ rescue StandardError
146
+ ""
147
+ end
148
+ end
149
+ end
150
+ end