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,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "uri"
4
+
5
+ module TempmailSdk
6
+ module Providers
7
+ # TempFastMail 渠道 — https://tempfastmail.com
8
+ module Tempfastmail
9
+ CHANNEL = "tempfastmail"
10
+ BASE_URL = "https://tempfastmail.com"
11
+ HEADERS = { "Accept" => "application/json", "User-Agent" => "Mozilla/5.0" }.freeze
12
+
13
+ module_function
14
+
15
+ def flatten(raw, recipient)
16
+ {
17
+ "id" => raw["uuid"],
18
+ "from" => raw["from"],
19
+ "to" => raw["real_to"] || recipient,
20
+ "subject" => raw["subject"],
21
+ "html" => raw["html"],
22
+ "date" => raw["received_at"],
23
+ "attachments" => raw["attachments"]
24
+ }
25
+ end
26
+
27
+ # 创建临时邮箱
28
+ # @return [EmailInfo]
29
+ def generate_email
30
+ resp = Http.post("#{BASE_URL}/api/email-box", headers: HEADERS)
31
+ resp.raise_for_status
32
+ data = resp.json
33
+ email = (data["email"] || "").strip
34
+ uuid = (data["uuid"] || "").strip
35
+ raise "tempfastmail: 创建邮箱响应无效" if email.empty? || !email.include?("@") || uuid.empty?
36
+
37
+ EmailInfo.new(channel: CHANNEL, email: email, token: uuid)
38
+ end
39
+
40
+ # 获取邮件列表
41
+ # @param token [String] UUID
42
+ # @param email [String]
43
+ # @return [Array<Email>]
44
+ def get_emails(token, email)
45
+ uuid = (token || "").strip
46
+ address = (email || "").strip
47
+ raise "tempfastmail: token 为空" if uuid.empty?
48
+ raise "tempfastmail: 邮箱地址为空" if address.empty?
49
+
50
+ resp = Http.get("#{BASE_URL}/api/email-box/#{URI.encode_www_form_component(uuid)}/emails",
51
+ headers: HEADERS)
52
+ resp.raise_for_status
53
+ rows = resp.json
54
+ return [] unless rows.is_a?(Array)
55
+
56
+ rows.filter_map do |row|
57
+ raw = row.is_a?(Hash) ? row : {}
58
+ message_id = (raw["uuid"] || "").strip
59
+ unless message_id.empty?
60
+ begin
61
+ dr = Http.get("#{BASE_URL}/api/email-box/#{URI.encode_www_form_component(uuid)}/email/#{URI.encode_www_form_component(message_id)}",
62
+ headers: HEADERS)
63
+ if dr.ok?
64
+ detail = dr.json
65
+ raw = detail if detail.is_a?(Hash)
66
+ end
67
+ rescue StandardError
68
+ # 忽略
69
+ end
70
+ end
71
+ Normalize.normalize_email(flatten(raw, address), address)
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,126 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module TempmailSdk
6
+ module Providers
7
+ # TempGmailer 渠道 — https://tempgmailer.com
8
+ # Laravel + CSRF,POST /get-gmail 创建,POST /get-inbox 获取邮件
9
+ module Tempgmailer
10
+ CHANNEL = "tempgmailer"
11
+ BASE_URL = "https://tempgmailer.com"
12
+
13
+ # 从 HTML 中提取 CSRF token
14
+ CSRF_RE = /<meta\s+name="csrf-token"\s+content="([^"]+)"/
15
+
16
+ HEADERS = {
17
+ "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",
18
+ "Accept" => "application/json, text/plain, */*",
19
+ "Accept-Language" => "zh-CN,zh;q=0.9,en;q=0.8",
20
+ "Referer" => "#{BASE_URL}/",
21
+ "X-Requested-With" => "XMLHttpRequest",
22
+ "X-TempGmailer-Auth" => "frontend",
23
+ "Content-Type" => "application/json"
24
+ }.freeze
25
+
26
+ module_function
27
+
28
+ def extract_cookies(resp)
29
+ cookies = {}
30
+ resp.set_cookies.each do |line|
31
+ pair = line.split(";").first.to_s.strip
32
+ k, v = pair.split("=", 2)
33
+ cookies[k] = v if k && v
34
+ end
35
+ cookies
36
+ end
37
+
38
+ def init_session
39
+ resp = Http.get(BASE_URL, headers: {
40
+ "User-Agent" => HEADERS["User-Agent"],
41
+ "Accept" => "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
42
+ }, timeout: 15)
43
+ resp.raise_for_status
44
+
45
+ m = resp.body.match(CSRF_RE)
46
+ raise "tempgmailer: 无法提取 CSRF token" unless m
47
+
48
+ csrf_token = m[1]
49
+ cookies = extract_cookies(resp)
50
+ cookie_str = cookies.map { |k, v| "#{k}=#{v}" }.join("; ")
51
+ raise "tempgmailer: 未获取到 session cookie" if cookie_str.empty?
52
+
53
+ [csrf_token, cookie_str]
54
+ end
55
+
56
+ # 创建临时邮箱
57
+ # @return [EmailInfo]
58
+ def generate_email
59
+ csrf_token, cookie_str = init_session
60
+
61
+ headers = HEADERS.merge(
62
+ "X-CSRF-TOKEN" => csrf_token,
63
+ "Cookie" => cookie_str
64
+ )
65
+
66
+ resp = Http.post("#{BASE_URL}/get-gmail", headers: headers,
67
+ json: { "refresh" => true, "adblock" => 0 })
68
+ resp.raise_for_status
69
+ data = resp.json
70
+ raise "tempgmailer: 创建邮箱失败: #{data['message'] || '未知错误'}" unless data["success"]
71
+
72
+ email = ((data["data"] || {})["email"] || "").strip
73
+ raise "tempgmailer: 创建邮箱失败,未返回邮箱地址" if email.empty?
74
+
75
+ token_payload = JSON.generate({ "csrfToken" => csrf_token, "cookies" => cookie_str })
76
+ EmailInfo.new(channel: CHANNEL, email: email, token: token_payload)
77
+ end
78
+
79
+ # 获取邮件列表
80
+ # @param token [String] JSON 格式认证信息
81
+ # @param email [String]
82
+ # @return [Array<Email>]
83
+ def get_emails(token, email)
84
+ session = JSON.parse(token)
85
+ csrf_token = session["csrfToken"]
86
+ cookie_str = session["cookies"]
87
+
88
+ headers = HEADERS.merge(
89
+ "X-CSRF-TOKEN" => csrf_token,
90
+ "Cookie" => cookie_str
91
+ )
92
+
93
+ resp = Http.post("#{BASE_URL}/get-inbox", headers: headers,
94
+ json: { "email" => email, "adblock" => 0 })
95
+ resp.raise_for_status
96
+ data = resp.json
97
+ return [] unless data["success"]
98
+
99
+ messages = (data["data"] || {})["messages"] || []
100
+ return [] unless messages.is_a?(Array)
101
+
102
+ messages.filter_map do |msg|
103
+ next unless msg.is_a?(Hash)
104
+
105
+ from_field = msg["from"]
106
+ from_field = from_field["address"] if from_field.is_a?(Hash)
107
+ from_field = from_field.to_s
108
+
109
+ html_content = msg["body"] || msg["intro"] || ""
110
+ text_content = msg["intro"] || ""
111
+
112
+ raw = {
113
+ "id" => msg["id"] || "",
114
+ "from" => from_field,
115
+ "to" => email,
116
+ "subject" => msg["subject"] || "",
117
+ "text" => text_content,
118
+ "html" => html_content,
119
+ "date" => msg["createdAt"] || ""
120
+ }
121
+ Normalize.normalize_email(raw, email)
122
+ end
123
+ end
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "uri"
4
+
5
+ module TempmailSdk
6
+ module Providers
7
+ # tempgo-email 渠道 — https://tempgo.email
8
+ # POST /api/generate 创建邮箱,GET /api/inbox 获取邮件
9
+ module TempgoEmail
10
+ CHANNEL = "tempgo-email"
11
+ BASE_URL = "https://tempgo.email"
12
+
13
+ HEADERS = {
14
+ "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",
15
+ "Accept" => "application/json"
16
+ }.freeze
17
+
18
+ module_function
19
+
20
+ # 创建临时邮箱
21
+ # @return [EmailInfo]
22
+ def generate_email
23
+ resp = Http.post("#{BASE_URL}/api/generate", headers: HEADERS, timeout: 15)
24
+ resp.raise_for_status
25
+ body = resp.json
26
+ raise "tempgo-email: 响应格式无效" unless body.is_a?(Hash)
27
+
28
+ address = body["email"] || ""
29
+ mailbox_id = body["mailbox_id"] || ""
30
+ raise "tempgo-email: 缺少邮箱地址" if address.empty?
31
+ raise "tempgo-email: 缺少 mailbox_id" if mailbox_id.empty?
32
+
33
+ EmailInfo.new(channel: CHANNEL, email: address, token: mailbox_id)
34
+ end
35
+
36
+ # 获取邮件列表
37
+ # @param token [String] mailbox_id
38
+ # @param email [String]
39
+ # @return [Array<Email>]
40
+ def get_emails(token, email)
41
+ raise "tempgo-email: token 不能为空" if token.nil? || token.empty?
42
+
43
+ address = (email || "").strip
44
+ raise "tempgo-email: 缺少邮箱地址" if address.empty?
45
+
46
+ resp = Http.get("#{BASE_URL}/api/inbox?email=#{URI.encode_www_form_component(address)}&mailbox_id=#{URI.encode_www_form_component(token)}",
47
+ headers: HEADERS, timeout: 15)
48
+ return [] if resp.status_code == 404
49
+ resp.raise_for_status
50
+
51
+ body = resp.json
52
+ return [] unless body.is_a?(Hash)
53
+
54
+ messages = body["messages"]
55
+ return [] unless messages.is_a?(Array) && !messages.empty?
56
+
57
+ messages.filter_map do |msg|
58
+ next unless msg.is_a?(Hash)
59
+
60
+ raw = {
61
+ "id" => msg["id"] || "",
62
+ "from" => msg["from"] || msg["sender"] || "",
63
+ "to" => address,
64
+ "subject" => msg["subject"] || "",
65
+ "text" => msg["text"] || msg["body_plain"] || "",
66
+ "html" => msg["html"] || msg["body_html"] || "",
67
+ "date" => (msg["date"] || msg["received_at"] || "").to_s
68
+ }
69
+ Normalize.normalize_email(raw, address)
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TempmailSdk
4
+ module Providers
5
+ # TempInbox 渠道实现
6
+ # API: https://endpoint.tempinbox.xyz
7
+ module Tempinbox
8
+ CHANNEL = "tempinbox"
9
+ BASE = "https://endpoint.tempinbox.xyz"
10
+
11
+ # 支持的域名列表
12
+ DOMAINS = %w[tempinbox.xyz thepiratebay.cloud cryptoblad.nl].freeze
13
+
14
+ HEADERS = {
15
+ "Accept" => "application/json, text/plain, */*",
16
+ "Cache-Control" => "no-cache",
17
+ "DNT" => "1",
18
+ "Referer" => "https://www.tempinbox.xyz/",
19
+ "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"
20
+ }.freeze
21
+
22
+ module_function
23
+
24
+ # 生成随机用户名
25
+ def random_user(length = 10)
26
+ chars = ("a".."z").to_a + ("0".."9").to_a
27
+ Array.new(length) { chars.sample }.join
28
+ end
29
+
30
+ # 创建临时邮箱
31
+ # @param domain [String, nil]
32
+ # @return [EmailInfo]
33
+ def generate_email(domain = nil)
34
+ if domain && !domain.to_s.strip.empty?
35
+ d = domain.strip.downcase
36
+ raise "tempinbox: 域名不可用: #{d}" unless DOMAINS.include?(d)
37
+
38
+ user = random_user
39
+ addr = "#{user}@#{d}"
40
+ Http.get("#{BASE}/email/#{addr}", headers: HEADERS, timeout: 15).raise_for_status
41
+ else
42
+ resp = Http.get("#{BASE}/email/Random", headers: HEADERS, timeout: 15)
43
+ resp.raise_for_status
44
+ addr = resp.body.strip.delete('"')
45
+ end
46
+
47
+ raise "tempinbox: 无效的邮箱地址" if addr.to_s.empty? || !addr.include?("@")
48
+
49
+ EmailInfo.new(channel: CHANNEL, email: addr)
50
+ end
51
+
52
+ # 获取邮件列表
53
+ # @param email [String]
54
+ # @return [Array<Email>]
55
+ def get_emails(email)
56
+ addr = email.to_s.strip
57
+ raise "tempinbox: 邮箱地址为空" if addr.empty?
58
+
59
+ resp = Http.get("#{BASE}/messages/#{addr}", headers: HEADERS, timeout: 15)
60
+ resp.raise_for_status
61
+ data = resp.json
62
+ return [] unless data.is_a?(Array)
63
+
64
+ data.select { |raw| raw.is_a?(Hash) }.map do |raw|
65
+ Normalize.normalize_email(raw, addr)
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TempmailSdk
4
+ module Providers
5
+ # tempmail.ing 渠道实现
6
+ # API: https://api.tempmail.ing/api
7
+ module Tempmail
8
+ CHANNEL = "tempmail"
9
+ BASE_URL = "https://api.tempmail.ing/api"
10
+
11
+ DEFAULT_HEADERS = {
12
+ "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",
13
+ "Content-Type" => "application/json",
14
+ "Referer" => "https://tempmail.ing/",
15
+ "DNT" => "1"
16
+ }.freeze
17
+
18
+ module_function
19
+
20
+ # 创建临时邮箱,支持自定义有效期(分钟)
21
+ # @param duration [Integer]
22
+ # @return [EmailInfo]
23
+ def generate_email(duration = 30)
24
+ resp = Http.post("#{BASE_URL}/generate", headers: DEFAULT_HEADERS, json: { "duration" => duration })
25
+ resp.raise_for_status
26
+ data = resp.json
27
+ raise "Failed to generate email" unless data["success"]
28
+
29
+ info = data["email"]
30
+ EmailInfo.new(
31
+ channel: CHANNEL,
32
+ email: info["address"],
33
+ expires_at: info["expiresAt"],
34
+ created_at: info["createdAt"]
35
+ )
36
+ end
37
+
38
+ # 获取邮件列表
39
+ # @param email [String]
40
+ # @return [Array<Email>]
41
+ def get_emails(email)
42
+ resp = Http.get("#{BASE_URL}/emails/#{URI.encode_www_form_component(email)}", headers: DEFAULT_HEADERS)
43
+ resp.raise_for_status
44
+ data = resp.json
45
+ raise "Failed to get emails" unless data["success"]
46
+
47
+ (data["emails"] || []).map { |raw| Normalize.normalize_email(flatten(raw, email), email) }
48
+ end
49
+
50
+ # 将原始格式扁平化:content -> html, from_address -> from, received_at -> date
51
+ def flatten(raw, recipient)
52
+ {
53
+ "id" => raw["id"] || "",
54
+ "from" => raw["from_address"] || raw["from"] || "",
55
+ "to" => recipient,
56
+ "subject" => raw["subject"] || "",
57
+ "text" => raw["text"] || "",
58
+ "html" => raw["content"] || raw["html"] || "",
59
+ "date" => raw["received_at"] || raw["date"] || "",
60
+ "is_read" => raw["is_read"] == 1 || raw["is_read"] == true,
61
+ "attachments" => raw["attachments"] || []
62
+ }
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "time"
4
+
5
+ module TempmailSdk
6
+ module Providers
7
+ # Tempmail365 渠道实现
8
+ # API: https://tempmail365.cn/tempemail.php
9
+ module Tempmail365
10
+ CHANNEL = "tempmail365"
11
+ BASE = "https://tempmail365.cn/tempemail.php"
12
+
13
+ # 后备域名列表
14
+ FALLBACK_DOMAINS = %w[fengyou.cc shop345.com nutemail.com qvrf.cn].freeze
15
+
16
+ HEADERS = {
17
+ "Accept" => "application/json, text/plain, */*",
18
+ "Cache-Control" => "no-cache",
19
+ "DNT" => "1",
20
+ "Referer" => "https://tempmail365.cn/",
21
+ "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"
22
+ }.freeze
23
+
24
+ module_function
25
+
26
+ # 从 API 获取可用域名列表
27
+ def fetch_domains
28
+ resp = Http.get("#{BASE}?action=get_config", headers: HEADERS, timeout: 15)
29
+ resp.raise_for_status
30
+ data = resp.json
31
+ return FALLBACK_DOMAINS.dup unless data.is_a?(Hash)
32
+
33
+ raw = data["domains"]
34
+ return FALLBACK_DOMAINS.dup unless raw.is_a?(Array) && !raw.empty?
35
+
36
+ out = raw.select { |d| d.is_a?(String) && !d.strip.empty? }.map(&:strip)
37
+ out.empty? ? FALLBACK_DOMAINS.dup : out
38
+ rescue StandardError
39
+ FALLBACK_DOMAINS.dup
40
+ end
41
+
42
+ # 生成随机用户名
43
+ def random_username(length = 8)
44
+ chars = ("a".."z").to_a + ("0".."9").to_a
45
+ Array.new(length) { chars.sample }.join
46
+ end
47
+
48
+ # 从 HTML 邮件内容中提取发件人
49
+ def extract_sender(content)
50
+ m = content.match(/(?:发件人|From)\s*[::]\s*(.+?)(?:<br|<\/|<p|\n|\r)/i)
51
+ m ? m[1].strip : ""
52
+ end
53
+
54
+ # 从 HTML 邮件内容中提取主题
55
+ def extract_subject(content)
56
+ m = content.match(/(?:主题|Subject)\s*[::]\s*(.+?)(?:<br|<\/|<p|\n|\r)/i)
57
+ m ? m[1].strip : ""
58
+ end
59
+
60
+ # 创建临时邮箱
61
+ # @param domain [String, nil]
62
+ # @return [EmailInfo]
63
+ def generate_email(domain = nil)
64
+ domains = fetch_domains
65
+ raise "tempmail365: 无可用域名" if domains.empty?
66
+
67
+ if domain && !domain.to_s.strip.empty?
68
+ d = domain.strip.downcase
69
+ matched = domains.select { |x| x.downcase == d }
70
+ raise "tempmail365: 域名不可用: #{d}" if matched.empty?
71
+
72
+ selected = matched.first
73
+ else
74
+ selected = domains.sample
75
+ end
76
+
77
+ username = random_username
78
+ addr = "#{username}@#{selected}"
79
+
80
+ url = "#{BASE}?action=create_email&email=#{URI.encode_www_form_component(addr)}&domain=#{URI.encode_www_form_component(selected)}"
81
+ resp = Http.get(url, headers: HEADERS, timeout: 15)
82
+ resp.raise_for_status
83
+ data = resp.json
84
+ raise "tempmail365: 创建邮箱失败" unless data.is_a?(Hash) && data["success"]
85
+
86
+ EmailInfo.new(channel: CHANNEL, email: addr)
87
+ end
88
+
89
+ # 获取邮件列表
90
+ # @param email [String]
91
+ # @return [Array<Email>]
92
+ def get_emails(email)
93
+ addr = email.to_s.strip
94
+ raise "tempmail365: 邮箱地址为空" if addr.empty?
95
+
96
+ resp = Http.get("#{BASE}?action=fetch_mail&email=#{URI.encode_www_form_component(addr)}",
97
+ headers: HEADERS, timeout: 15)
98
+ resp.raise_for_status
99
+ data = resp.json
100
+ return [] unless data.is_a?(Hash)
101
+
102
+ content = data["content"]
103
+ return [] if content.nil? || !content.is_a?(String) || content.strip.empty?
104
+ return [] if content.strip == "无邮件"
105
+
106
+ sender = extract_sender(content)
107
+ subject = extract_subject(content)
108
+
109
+ raw = {
110
+ "from" => sender,
111
+ "subject" => subject,
112
+ "body" => content,
113
+ "date" => Time.now.utc.iso8601
114
+ }
115
+ [Normalize.normalize_email(raw, addr)]
116
+ end
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "uri"
5
+
6
+ module TempmailSdk
7
+ module Providers
8
+ # TempMail Fish 渠道 — https://tempmail.fish
9
+ # POST /emails/new-email 创建邮箱,GET /emails/emails 获取邮件
10
+ module TempmailFish
11
+ CHANNEL = "tempmail-fish"
12
+ API_BASE = "https://api.tempmail.fish"
13
+ HEADERS = {
14
+ "Accept" => "application/json",
15
+ "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"
16
+ }.freeze
17
+
18
+ module_function
19
+
20
+ # 将毫秒时间戳转换为 ISO8601 字符串
21
+ def to_iso(timestamp)
22
+ return "" if timestamp.nil?
23
+
24
+ millis = Float(timestamp)
25
+ Time.at(millis / 1000.0).utc.iso8601
26
+ rescue ArgumentError, TypeError
27
+ ""
28
+ end
29
+
30
+ # @return [EmailInfo]
31
+ def generate_email
32
+ resp = Http.post("#{API_BASE}/emails/new-email",
33
+ headers: HEADERS.merge("Content-Type" => "application/json"))
34
+ resp.raise_for_status
35
+ data = resp.json
36
+ raise "tempmail-fish: 创建邮箱响应无效" unless data.is_a?(Hash)
37
+
38
+ email = (data["email"] || "").strip
39
+ auth_key = (data["authKey"] || "").strip
40
+ raise "tempmail-fish: 创建邮箱响应无效" if email.empty? || !email.include?("@") || auth_key.empty?
41
+
42
+ token = JSON.generate({ "email" => email, "authKey" => auth_key })
43
+ EmailInfo.new(channel: CHANNEL, email: email, token: token)
44
+ end
45
+
46
+ # @param token [String] JSON 字符串
47
+ # @param email [String]
48
+ # @return [Array<Email>]
49
+ def get_emails(token, email)
50
+ raise "tempmail-fish: token 不能为空" if token.nil? || token.empty?
51
+
52
+ parsed = JSON.parse(token)
53
+ raise "tempmail-fish: token 格式无效" unless parsed.is_a?(Hash)
54
+
55
+ address = (parsed["email"] || email || "").strip
56
+ auth_key = (parsed["authKey"] || "").strip
57
+ raise "tempmail-fish: token 缺少 email 或 authKey" if address.empty? || auth_key.empty?
58
+
59
+ resp = Http.get("#{API_BASE}/emails/emails?emailAddress=#{URI.encode_www_form_component(address)}",
60
+ headers: HEADERS.merge("Authorization" => auth_key))
61
+ resp.raise_for_status
62
+ data = resp.json
63
+
64
+ rows = if data.is_a?(Array)
65
+ data
66
+ elsif data.is_a?(Hash) && data["emails"].is_a?(Array)
67
+ data["emails"]
68
+ else
69
+ []
70
+ end
71
+
72
+ rows.filter_map do |row|
73
+ next unless row.is_a?(Hash)
74
+
75
+ raw = {
76
+ "from" => (row["from"] || "").to_s,
77
+ "to" => (row["to"] || "").to_s.empty? ? address : row["to"].to_s,
78
+ "subject" => (row["subject"] || "").to_s,
79
+ "text" => (row["textBody"] || "").to_s,
80
+ "html" => (row["htmlBody"] || "").to_s,
81
+ "date" => to_iso(row["timestamp"]),
82
+ "attachments" => row["attachments"] || []
83
+ }
84
+ Normalize.normalize_email(raw, address)
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end