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