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,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TempmailSdk
4
+ module Providers
5
+ # anonymmail.net 渠道实现
6
+ # POST JSON API,需要 cookie session
7
+ module Anonymmail
8
+ CHANNEL = "anonymmail"
9
+ BASE = "https://anonymmail.net"
10
+ HEADERS = {
11
+ "Accept" => "*/*",
12
+ "Content-Type" => "application/x-www-form-urlencoded",
13
+ "Referer" => "https://anonymmail.net/",
14
+ "User-Agent" => "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " \
15
+ "(KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36 Edg/146.0.0.0"
16
+ }.freeze
17
+
18
+ module_function
19
+
20
+ def random_username(length = 10)
21
+ chars = ("a".."z").to_a + ("0".."9").to_a
22
+ Array.new(length) { chars.sample }.join
23
+ end
24
+
25
+ # 获取可用域名列表
26
+ def fetch_domains
27
+ resp = Http.post("#{BASE}/api/getDomains", headers: HEADERS, timeout: 15)
28
+ resp.raise_for_status
29
+ data = resp.json
30
+ return [] unless data.is_a?(Array)
31
+
32
+ data.select { |item| item.is_a?(Hash) && item["domain"].to_s.strip != "" }
33
+ .map { |item| item["domain"].to_s.strip }
34
+ end
35
+
36
+ # @return [EmailInfo]
37
+ def generate_email
38
+ domains = fetch_domains
39
+ raise "anonymmail: no domains available" if domains.empty?
40
+
41
+ domain = domains.sample
42
+ username = random_username
43
+ email = "#{username}@#{domain}"
44
+
45
+ # HEAD 请求获取 cookie session
46
+ Http.get("#{BASE}/", headers: HEADERS, timeout: 15)
47
+
48
+ # POST 创建邮箱
49
+ resp = Http.post("#{BASE}/api/create", headers: HEADERS, body: "email=#{email}", timeout: 15)
50
+ resp.raise_for_status
51
+ data = resp.json
52
+ raise "anonymmail: create inbox failed" unless data.is_a?(Hash) && data["success"]
53
+
54
+ addr = data["email"].to_s.strip
55
+ raise "anonymmail: missing email in response" if addr.empty?
56
+
57
+ EmailInfo.new(channel: CHANNEL, email: addr)
58
+ end
59
+
60
+ # @param email [String]
61
+ # @return [Array<Email>]
62
+ def get_emails(email)
63
+ e = email.to_s.strip
64
+ raise "anonymmail: empty email" if e.empty?
65
+
66
+ resp = Http.post("#{BASE}/api/get", headers: HEADERS, body: "email=#{e}", timeout: 15)
67
+ resp.raise_for_status
68
+ data = resp.json
69
+ return [] unless data.is_a?(Hash)
70
+
71
+ inbox = data[e]
72
+ return [] unless inbox.is_a?(Hash)
73
+
74
+ rows = inbox["emails"]
75
+ return [] unless rows.is_a?(Array)
76
+
77
+ rows.select { |r| r.is_a?(Hash) }.map do |raw|
78
+ normalized = raw.dup
79
+ # 将 token 字段映射为 id
80
+ if normalized.key?("token") && !normalized.key?("id")
81
+ normalized["id"] = normalized.delete("token")
82
+ end
83
+ # 将 body 字段映射为 html
84
+ if normalized.key?("body") && !normalized.key?("html")
85
+ normalized["html"] = normalized.delete("body")
86
+ end
87
+ Normalize.normalize_email(normalized, e)
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TempmailSdk
4
+ module Providers
5
+ # byom.de 渠道实现
6
+ # 纯 GET 无认证,直接生成随机用户名
7
+ module Byom
8
+ CHANNEL = "byom"
9
+ DOMAIN = "byom.de"
10
+ BASE = "https://api.byom.de"
11
+ HEADERS = {
12
+ "Accept" => "application/json, text/plain, */*",
13
+ "User-Agent" => "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " \
14
+ "(KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36 Edg/146.0.0.0"
15
+ }.freeze
16
+
17
+ module_function
18
+
19
+ def random_username(length = 10)
20
+ chars = ("a".."z").to_a + ("0".."9").to_a
21
+ Array.new(length) { chars.sample }.join
22
+ end
23
+
24
+ # @return [EmailInfo]
25
+ def generate_email
26
+ username = random_username
27
+ EmailInfo.new(channel: CHANNEL, email: "#{username}@#{DOMAIN}")
28
+ end
29
+
30
+ # @param email [String]
31
+ # @return [Array<Email>]
32
+ def get_emails(email)
33
+ e = email.to_s.strip
34
+ raise "byom: empty email" if e.empty?
35
+
36
+ username = e.split("@").first.to_s
37
+ raise "byom: invalid email format" if username.empty?
38
+
39
+ resp = Http.get("#{BASE}/mails/#{username}", headers: HEADERS, timeout: 15)
40
+ resp.raise_for_status
41
+ data = resp.json
42
+ return [] unless data.is_a?(Array)
43
+
44
+ data.select { |r| r.is_a?(Hash) }.map { |raw| Normalize.normalize_email(raw, e) }
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "uri"
4
+
5
+ module TempmailSdk
6
+ module Providers
7
+ # Catchmail 渠道 — https://catchmail.io
8
+ # 匿名 REST API:创建邮箱即列表接口,逐封拉取详情
9
+ module Catchmail
10
+ CHANNEL = "catchmail"
11
+ API_BASE = "https://api.catchmail.io/api/v1"
12
+ DEFAULT_DOMAIN = "catchmail.io"
13
+ DOMAINS = %w[catchmail.io mailistry.com zeppost.com].freeze
14
+ HEADERS = {
15
+ "Accept" => "application/json",
16
+ "Referer" => "https://catchmail.io/",
17
+ "Origin" => "https://catchmail.io",
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
+ # 从可能带显示名的地址字段提取纯地址
39
+ def clean_address(value)
40
+ value = value.first if value.is_a?(Array)
41
+ text = value.to_s.strip
42
+ m = text.match(/<([^>]+)>/)
43
+ m ? m[1].strip : text
44
+ end
45
+
46
+ def flatten_detail(raw, recipient)
47
+ body = raw["body"].is_a?(Hash) ? raw["body"] : {}
48
+ {
49
+ "id" => raw["id"] || "", "from" => clean_address(raw["from"]),
50
+ "to" => (a = clean_address(raw["to"])).empty? ? recipient : a,
51
+ "subject" => raw["subject"] || "", "text" => body["text"] || "",
52
+ "html" => body["html"] || "", "date" => raw["date"] || "",
53
+ "attachments" => raw["attachments"]
54
+ }
55
+ end
56
+
57
+ def fetch_message(message_id, email)
58
+ resp = Http.get(
59
+ "#{API_BASE}/message/#{URI.encode_www_form_component(message_id)}?mailbox=#{URI.encode_www_form_component(email)}",
60
+ headers: HEADERS, timeout: 15
61
+ )
62
+ return nil unless resp.ok?
63
+
64
+ data = resp.json
65
+ data.is_a?(Hash) ? data : nil
66
+ end
67
+
68
+ # @param domain [String, nil]
69
+ # @return [EmailInfo]
70
+ def generate_email(domain = nil)
71
+ email = "#{random_local}@#{pick_domain(domain)}"
72
+ Http.get("#{API_BASE}/mailbox?address=#{URI.encode_www_form_component(email)}",
73
+ headers: HEADERS, timeout: 15).raise_for_status
74
+ EmailInfo.new(channel: CHANNEL, email: email)
75
+ end
76
+
77
+ # @param email [String]
78
+ # @return [Array<Email>]
79
+ def get_emails(email)
80
+ address = email.to_s.strip
81
+ raise "catchmail: empty email" if address.empty?
82
+
83
+ resp = Http.get("#{API_BASE}/mailbox?address=#{URI.encode_www_form_component(address)}",
84
+ headers: HEADERS, timeout: 15)
85
+ resp.raise_for_status
86
+ data = resp.json
87
+ rows = data.is_a?(Hash) ? data["messages"] : nil
88
+ return [] unless rows.is_a?(Array)
89
+
90
+ rows.filter_map do |item|
91
+ next unless item.is_a?(Hash)
92
+
93
+ message_id = item["id"].to_s.strip
94
+ next if message_id.empty?
95
+
96
+ detail = fetch_message(message_id, address)
97
+ if detail
98
+ Normalize.normalize_email(flatten_detail(detail, address), address)
99
+ else
100
+ Normalize.normalize_email({
101
+ "id" => message_id, "from" => clean_address(item["from"]),
102
+ "to" => item["mailbox"] || address,
103
+ "subject" => item["subject"] || "", "date" => item["date"] || ""
104
+ }, address)
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TempmailSdk
4
+ module Providers
5
+ # cleantempmail.com 渠道实现
6
+ module Cleantempmail
7
+ CHANNEL = "cleantempmail"
8
+ API_BASE = "https://cleantempmail.com/api"
9
+ HEADERS = {
10
+ "Accept" => "application/json",
11
+ "User-Agent" => "Mozilla/5.0",
12
+ "X-API-Key" => (ENV["CLEANTEMPMAIL_API_KEY"].to_s.strip.empty? ? "ct-test" : ENV["CLEANTEMPMAIL_API_KEY"].to_s.strip)
13
+ }.freeze
14
+
15
+ module_function
16
+
17
+ def fetch_json(path)
18
+ resp = Http.get("#{API_BASE}#{path}", headers: HEADERS, timeout: 15)
19
+ resp.raise_for_status
20
+ data = resp.json
21
+ data.is_a?(Hash) ? data : {}
22
+ end
23
+
24
+ # @return [EmailInfo]
25
+ def generate_email
26
+ data = fetch_json("/generate-email")
27
+ payload = data["data"].is_a?(Hash) ? data["data"] : {}
28
+ email = (payload["email"] || payload["mailbox"]).to_s.strip
29
+ raise "cleantempmail: invalid generate-email response" if email.empty? || !email.include?("@")
30
+
31
+ EmailInfo.new(channel: CHANNEL, email: email)
32
+ end
33
+
34
+ # @param email [String]
35
+ # @return [Array<Email>]
36
+ def get_emails(email)
37
+ address = email.to_s.strip
38
+ raise "cleantempmail: empty email" if address.empty?
39
+
40
+ data = fetch_json("/emails?email=#{URI.encode_www_form_component(address)}")
41
+ payload = data["data"].is_a?(Hash) ? data["data"] : {}
42
+ rows = payload["emails"]
43
+ return [] unless rows.is_a?(Array)
44
+
45
+ rows.select { |r| r.is_a?(Hash) }.map { |item| Normalize.normalize_email(item, address) }
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TempmailSdk
4
+ module Providers
5
+ # devmail.uk 渠道实现
6
+ module DevmailUk
7
+ CHANNEL = "devmail-uk"
8
+ API_BASE = "https://www.devmail.uk/api"
9
+ HEADERS = { "Accept" => "application/json" }.freeze
10
+
11
+ module_function
12
+
13
+ def mailbox_from_email(email)
14
+ address = email.to_s.strip
15
+ return "" if address.empty?
16
+ return address.split("@", 2).first.to_s.strip if address.include?("@")
17
+
18
+ address
19
+ end
20
+
21
+ # @return [EmailInfo]
22
+ def generate_email
23
+ resp = Http.get("#{API_BASE}/new", headers: HEADERS, timeout: 15)
24
+ resp.raise_for_status
25
+ data = resp.json
26
+ raise "devmail-uk: invalid new email response" unless data.is_a?(Hash)
27
+
28
+ email = data["email"].to_s.strip
29
+ if email.empty? && !data["mailbox"].to_s.strip.empty?
30
+ email = "#{data['mailbox'].to_s.strip}@devmail.uk"
31
+ end
32
+ raise "devmail-uk: invalid new email response" if email.empty? || !email.include?("@")
33
+
34
+ EmailInfo.new(channel: CHANNEL, email: email)
35
+ end
36
+
37
+ # @param email [String]
38
+ # @return [Array<Email>]
39
+ def get_emails(email)
40
+ mailbox = mailbox_from_email(email)
41
+ raise "devmail-uk: empty email" if mailbox.empty?
42
+
43
+ resp = Http.get(
44
+ "#{API_BASE}/inbox/#{URI.encode_www_form_component(mailbox)}?detail=true",
45
+ headers: HEADERS, timeout: 15
46
+ )
47
+ resp.raise_for_status
48
+ data = resp.json
49
+ return [] unless data.is_a?(Hash)
50
+
51
+ rows = data["emails"]
52
+ return [] unless rows.is_a?(Array)
53
+
54
+ rows.select { |r| r.is_a?(Hash) }.map { |item| Normalize.normalize_email(item, email) }
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,133 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "uri"
5
+
6
+ module TempmailSdk
7
+ module Providers
8
+ # dropmail.me GraphQL 渠道 — https://dropmail.me
9
+ # 使用 af_ 令牌访问 GraphQL API 创建 session 和查询邮件
10
+ module Dropmail
11
+ CHANNEL = "dropmail"
12
+ TOKEN_GENERATE_URL = "https://dropmail.me/api/token/generate"
13
+ TOKEN_RENEW_URL = "https://dropmail.me/api/token/renew"
14
+
15
+ CREATE_SESSION_QUERY = "mutation {introduceSession {id, expiresAt, addresses{id, address}}}"
16
+ GET_MAILS_QUERY = 'query ($id: ID!) { session(id:$id) { mails { id, rawSize, fromAddr, toAddr, receivedAt, text, headerFrom, headerSubject, html } } }'
17
+
18
+ TOKEN_HEADERS = {
19
+ "Accept" => "application/json",
20
+ "Content-Type" => "application/json",
21
+ "Origin" => "https://dropmail.me",
22
+ "Referer" => "https://dropmail.me/api/"
23
+ }.freeze
24
+
25
+ AUTO_CACHE_SEC = 50 * 60
26
+ RENEW_BEFORE_SEC = 10 * 60
27
+
28
+ @lock = Mutex.new
29
+ @cached_af = nil # [token_string, expiry_monotonic]
30
+
31
+ module_function
32
+
33
+ # 从配置或环境变量中获取显式 token
34
+ def explicit_af_token
35
+ ENV["DROPMAIL_AUTH_TOKEN"].to_s.strip.then do |t|
36
+ return t unless t.empty?
37
+ end
38
+ ENV["DROPMAIL_API_TOKEN"].to_s.strip
39
+ end
40
+
41
+ # 从 API 获取新 af_ token
42
+ def fetch_af_token
43
+ resp = Http.post(TOKEN_GENERATE_URL, headers: TOKEN_HEADERS, json: { "type" => "af", "lifetime" => "1h" })
44
+ resp.raise_for_status
45
+ body = resp.json
46
+ tok = (body["token"] || "").to_s.strip
47
+ raise "DropMail token/generate 未返回有效 af_ 令牌" if tok.empty? || !tok.start_with?("af_")
48
+
49
+ tok
50
+ end
51
+
52
+ # 解析 af_ token(优先显式配置,其次自动获取并缓存)
53
+ def resolve_af_token
54
+ ex = explicit_af_token
55
+ return ex unless ex.empty?
56
+
57
+ now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
58
+ @lock.synchronize do
59
+ if @cached_af
60
+ tok, exp = @cached_af
61
+ return tok if now < exp - RENEW_BEFORE_SEC
62
+ end
63
+ @cached_af = nil
64
+ new_tok = fetch_af_token
65
+ @cached_af = [new_tok, Process.clock_gettime(Process::CLOCK_MONOTONIC) + AUTO_CACHE_SEC]
66
+ new_tok
67
+ end
68
+ end
69
+
70
+ # 构造 GraphQL URL
71
+ def graphql_url
72
+ af = resolve_af_token
73
+ "https://dropmail.me/api/graphql/#{URI.encode_www_form_component(af)}"
74
+ end
75
+
76
+ # 发送 GraphQL 请求
77
+ def graphql_request(query, variables = nil)
78
+ data = { "query" => query }
79
+ data["variables"] = JSON.generate(variables) if variables
80
+
81
+ resp = Http.post(
82
+ graphql_url,
83
+ headers: { "Content-Type" => "application/x-www-form-urlencoded" },
84
+ body: URI.encode_www_form(data)
85
+ )
86
+ resp.raise_for_status
87
+ result = resp.json
88
+ if result["errors"]&.any?
89
+ raise "GraphQL error: #{result['errors'].first['message'] || 'Unknown'}"
90
+ end
91
+
92
+ result["data"] || {}
93
+ end
94
+
95
+ # 创建临时邮箱
96
+ # @return [EmailInfo]
97
+ def generate_email
98
+ data = graphql_request(CREATE_SESSION_QUERY)
99
+ session = data["introduceSession"]
100
+ raise "Failed to generate email" unless session && session["addresses"]&.any?
101
+
102
+ EmailInfo.new(
103
+ channel: CHANNEL,
104
+ email: session["addresses"][0]["address"],
105
+ token: session["id"],
106
+ expires_at: session["expiresAt"]
107
+ )
108
+ end
109
+
110
+ # 获取邮件列表
111
+ # @param token [String] session ID
112
+ # @param email [String]
113
+ # @return [Array<Email>]
114
+ def get_emails(token, email = "")
115
+ data = graphql_request(GET_MAILS_QUERY, { "id" => token })
116
+ mails = ((data["session"] || {})["mails"]) || []
117
+ mails.map do |m|
118
+ raw = {
119
+ "id" => m["id"] || "",
120
+ "from" => m["fromAddr"] || "",
121
+ "to" => m["toAddr"] || email,
122
+ "subject" => m["headerSubject"] || "",
123
+ "text" => m["text"] || "",
124
+ "html" => m["html"] || "",
125
+ "received_at" => m["receivedAt"] || "",
126
+ "attachments" => []
127
+ }
128
+ Normalize.normalize_email(raw, email)
129
+ end
130
+ end
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TempmailSdk
4
+ module Providers
5
+ # dropmail.click 渠道实现
6
+ # 独立后端临时邮箱,免注册、无鉴权。Token 即邮箱地址本身。
7
+ module DropmailClick
8
+ CHANNEL = "dropmail-click"
9
+ BASE_URL = "https://dropmail.click"
10
+ DEFAULT_UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " \
11
+ "(KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36"
12
+
13
+ module_function
14
+
15
+ def get_ua
16
+ cfg = Config.get_config
17
+ (cfg.headers || {})["User-Agent"] || DEFAULT_UA
18
+ end
19
+
20
+ def first_val(msg, *keys)
21
+ keys.each do |key|
22
+ val = msg[key]
23
+ return val.to_s if !val.nil? && val != ""
24
+ end
25
+ ""
26
+ end
27
+
28
+ # @return [EmailInfo]
29
+ def generate_email
30
+ resp = Http.post(
31
+ "#{BASE_URL}/api/v1/public/mailbox",
32
+ headers: { "User-Agent" => get_ua, "Accept" => "application/json" }
33
+ )
34
+ resp.raise_for_status
35
+ data = resp.json
36
+ raise "dropmail-click: invalid response" unless data.is_a?(Hash)
37
+
38
+ address = data["address"].to_s
39
+ raise "dropmail-click: missing address" if address.empty?
40
+
41
+ EmailInfo.new(
42
+ channel: CHANNEL,
43
+ email: address,
44
+ token: address,
45
+ expires_at: data["expires_at"]
46
+ )
47
+ end
48
+
49
+ # @param email [String]
50
+ # @return [Array<Email>]
51
+ def get_emails(email)
52
+ addr = email.to_s.strip
53
+ raise "dropmail-click: missing email" if addr.empty?
54
+
55
+ resp = Http.get(
56
+ "#{BASE_URL}/api/v1/public/mailbox/#{URI.encode_www_form_component(addr)}",
57
+ headers: { "User-Agent" => get_ua, "Accept" => "application/json" }
58
+ )
59
+ resp.raise_for_status
60
+ data = resp.json
61
+ return [] unless data.is_a?(Hash)
62
+
63
+ messages = data["messages"]
64
+ return [] unless messages.is_a?(Array)
65
+
66
+ messages.select { |m| m.is_a?(Hash) }.map do |msg|
67
+ row = {
68
+ "id" => first_val(msg, "id"),
69
+ "from" => first_val(msg, "from"),
70
+ "to" => first_val(msg, "address").empty? ? addr : first_val(msg, "address"),
71
+ "subject" => first_val(msg, "subject"),
72
+ "text" => first_val(msg, "text"),
73
+ "html" => first_val(msg, "html"),
74
+ "date" => first_val(msg, "received_at", "date")
75
+ }
76
+ Normalize.normalize_email(row, addr)
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end