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,135 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TempmailSdk
4
+ module Providers
5
+ # freecustom.email 渠道实现
6
+ # 免注册临时邮箱,任意 local part @ 可用域名即时可收信
7
+ module Freecustom
8
+ CHANNEL = "freecustom"
9
+ SITE_URL = "https://www.freecustom.email"
10
+ DOMAINS_URL = "https://api2.freecustom.email/domains"
11
+ REFERER = "https://www.freecustom.email/en"
12
+ DEFAULT_UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " \
13
+ "(KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36"
14
+
15
+ module_function
16
+
17
+ def get_ua
18
+ cfg = Config.get_config
19
+ (cfg.headers || {})["User-Agent"] || DEFAULT_UA
20
+ end
21
+
22
+ def random_local(n = 10)
23
+ chars = ("a".."z").to_a + ("0".."9").to_a
24
+ Array.new(n) { chars.sample }.join
25
+ end
26
+
27
+ def first_val(msg, *keys)
28
+ keys.each do |key|
29
+ val = msg[key]
30
+ return val.to_s if !val.nil? && val != ""
31
+ end
32
+ ""
33
+ end
34
+
35
+ # 挑选一个可收信域名
36
+ def pick_domain
37
+ resp = Http.get(
38
+ DOMAINS_URL,
39
+ headers: { "User-Agent" => get_ua, "Accept" => "application/json", "Referer" => REFERER }
40
+ )
41
+ resp.raise_for_status
42
+ data = resp.json
43
+ lst = data.is_a?(Hash) ? data["data"] : nil
44
+ raise "freecustom: domain list empty" unless lst.is_a?(Array) && !lst.empty?
45
+
46
+ usable = lst.select do |d|
47
+ d.is_a?(Hash) && d["tier"] == "free" && d["expiring_soon"] != true && !d["domain"].to_s.empty?
48
+ end
49
+ pool = usable.empty? ? lst.select { |d| d.is_a?(Hash) && !d["domain"].to_s.empty? } : usable
50
+ raise "freecustom: no usable domain" if pool.empty?
51
+
52
+ pool.sample["domain"].to_s
53
+ end
54
+
55
+ # 获取匿名访问令牌
56
+ def fetch_auth_token
57
+ resp = Http.post(
58
+ "#{SITE_URL}/api/auth",
59
+ headers: {
60
+ "User-Agent" => get_ua, "Accept" => "application/json",
61
+ "Content-Type" => "application/json", "Referer" => REFERER
62
+ }
63
+ )
64
+ resp.raise_for_status
65
+ data = resp.json
66
+ raise "freecustom: invalid token response" unless data.is_a?(Hash) && !data["token"].to_s.empty?
67
+
68
+ data["token"].to_s
69
+ end
70
+
71
+ # @return [EmailInfo]
72
+ def generate_email
73
+ domain = pick_domain
74
+ email = "#{random_local}@#{domain}"
75
+ EmailInfo.new(channel: CHANNEL, email: email, token: email)
76
+ end
77
+
78
+ # @param email [String]
79
+ # @return [Array<Email>]
80
+ def get_emails(email)
81
+ addr = email.to_s.strip
82
+ raise "freecustom: missing email" if addr.empty?
83
+
84
+ jwt = fetch_auth_token
85
+ auth_headers = {
86
+ "User-Agent" => get_ua, "Accept" => "application/json",
87
+ "Referer" => REFERER, "Authorization" => "Bearer #{jwt}",
88
+ "x-fce-client" => "web-client"
89
+ }
90
+
91
+ list_resp = Http.get(
92
+ "#{SITE_URL}/api/public-mailbox?fullMailboxId=#{URI.encode_www_form_component(addr)}",
93
+ headers: auth_headers
94
+ )
95
+ list_resp.raise_for_status
96
+ list_data = list_resp.json
97
+ return [] unless list_data.is_a?(Hash) && list_data["success"]
98
+
99
+ items = list_data["data"]
100
+ return [] unless items.is_a?(Array)
101
+
102
+ items.select { |i| i.is_a?(Hash) && i["id"] }.map do |item|
103
+ msg_id = item["id"].to_s
104
+ full = item
105
+ begin
106
+ msg_resp = Http.get(
107
+ "#{SITE_URL}/api/public-mailbox?fullMailboxId=#{URI.encode_www_form_component(addr)}" \
108
+ "&messageId=#{URI.encode_www_form_component(msg_id)}",
109
+ headers: auth_headers
110
+ )
111
+ if msg_resp.ok?
112
+ msg_data = msg_resp.json
113
+ if msg_data.is_a?(Hash) && msg_data["success"] && msg_data["data"].is_a?(Hash)
114
+ full = msg_data["data"]
115
+ end
116
+ end
117
+ rescue StandardError
118
+ # 正文补全失败时退回列表元数据
119
+ end
120
+
121
+ row = {
122
+ "id" => first_val(full, "id"),
123
+ "from" => first_val(full, "from"),
124
+ "to" => first_val(full, "to").empty? ? addr : first_val(full, "to"),
125
+ "subject" => first_val(full, "subject"),
126
+ "text" => first_val(full, "text"),
127
+ "html" => first_val(full, "html"),
128
+ "date" => first_val(full, "date")
129
+ }
130
+ Normalize.normalize_email(row, addr)
131
+ end
132
+ end
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,146 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "securerandom"
4
+ require "uri"
5
+
6
+ module TempmailSdk
7
+ module Providers
8
+ # GetNada 渠道实现 -- https://getnada.net
9
+ # 匿名 JSON API:拉取公开域名 -> 开信箱换取 token -> 拉取邮件列表/详情
10
+ module Getnada
11
+ CHANNEL = "getnada"
12
+ API_BASE = "https://getnada.net/api"
13
+ HEADERS_JSON = { "Accept" => "application/json" }.freeze
14
+ HEADERS_POST = { "Accept" => "application/json", "Content-Type" => "application/json" }.freeze
15
+ # 单个域名标签校验(大小写不敏感)
16
+ DOMAIN_LABEL_RE = /\A[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\z/i.freeze
17
+
18
+ module_function
19
+
20
+ # 生成随机本地部分:sdk + 16 位小写字母数字
21
+ def random_local
22
+ chars = ("a".."z").to_a + ("0".."9").to_a
23
+ "sdk" + Array.new(16) { chars.sample }.join
24
+ end
25
+
26
+ # 清洗并校验域名,非法返回空串
27
+ def clean_domain(value)
28
+ domain = value.to_s.strip.downcase
29
+ return "" if domain.empty? || domain.length > 253 || domain.include?("..")
30
+
31
+ labels = domain.split(".")
32
+ return "" if labels.length < 2
33
+ return "" unless labels.all? { |label| DOMAIN_LABEL_RE.match?(label) }
34
+
35
+ domain
36
+ end
37
+
38
+ # 拉取公开域名列表,按偏好挑选,缺省优先 getnada.net
39
+ # @param preferred [String, nil]
40
+ # @return [String]
41
+ def pick_domain(preferred = nil)
42
+ resp = Http.get("#{API_BASE}/public/domains", headers: HEADERS_JSON, timeout: 15)
43
+ resp.raise_for_status
44
+ data = resp.json
45
+ domains = data.is_a?(Hash) ? data["domains"] : []
46
+ domains = [] unless domains.is_a?(Array)
47
+ cleaned = domains.map { |d| clean_domain(d) }.reject(&:empty?)
48
+
49
+ wanted = preferred.to_s.strip.sub(/\A@/, "").downcase
50
+ unless wanted.empty?
51
+ found = cleaned.find { |d| d == wanted }
52
+ return found if found
53
+
54
+ raise "getnada: domain not available: #{wanted}"
55
+ end
56
+
57
+ default = cleaned.find { |d| d == "getnada.net" }
58
+ return default if default
59
+ return cleaned.first unless cleaned.empty?
60
+
61
+ raise "getnada: no domain available"
62
+ end
63
+
64
+ # 将原始邮件对象展平为 normalize 可识别的字段
65
+ def flatten_message(raw, recipient)
66
+ out = raw.dup
67
+ out["id"] = raw["id"]
68
+ out["from"] = first_non_empty(raw["from_addr"], raw["from"])
69
+ out["to"] = first_non_empty(raw["to"]).empty? ? recipient : raw["to"]
70
+ out["text"] = first_non_empty(raw["text_plain"], raw["text"])
71
+ out["html"] = first_non_empty(raw["html_sanitized"], raw["html"])
72
+ out["read"] = !raw["read_at"].to_s.empty? if raw.key?("read_at")
73
+ out
74
+ end
75
+
76
+ def first_non_empty(*values)
77
+ values.each do |v|
78
+ next if v.nil?
79
+
80
+ t = v.to_s
81
+ return t unless t.empty?
82
+ end
83
+ ""
84
+ end
85
+
86
+ # 生成临时邮箱:选域名 -> 开信箱 -> 取 token/recipient
87
+ # @param domain [String, nil] 指定域名(多渠道别名派生用)
88
+ # @param channel [String] 渠道标识
89
+ # @return [EmailInfo]
90
+ def generate_email(domain = nil, channel = CHANNEL)
91
+ selected_domain = pick_domain(domain)
92
+ requested = "#{random_local}@#{selected_domain}"
93
+ resp = Http.post("#{API_BASE}/inbox/open", headers: HEADERS_POST,
94
+ json: { "email" => requested }, timeout: 15)
95
+ resp.raise_for_status
96
+ data = resp.json
97
+ raise "getnada: invalid open response" unless data.is_a?(Hash)
98
+
99
+ token = data["token"].to_s.strip
100
+ email = (data["recipient"] || requested).to_s.strip
101
+ raise "getnada: invalid open response" if token.empty? || email.empty? || !email.include?("@")
102
+
103
+ EmailInfo.new(channel: channel, email: email, token: token, expires_at: data["activeUntil"])
104
+ end
105
+
106
+ # 拉取单封邮件详情,失败返回 nil
107
+ def fetch_detail(token, message_id)
108
+ url = "#{API_BASE}/inbox/message?id=#{URI.encode_www_form_component(message_id)}" \
109
+ "&token=#{URI.encode_www_form_component(token)}"
110
+ resp = Http.get(url, headers: HEADERS_JSON, timeout: 15)
111
+ return nil unless resp.ok?
112
+
113
+ data = resp.json
114
+ return data["message"] if data.is_a?(Hash) && data["message"].is_a?(Hash)
115
+
116
+ nil
117
+ end
118
+
119
+ # 拉取邮件列表并逐封获取详情
120
+ # @param token [String]
121
+ # @param email [String]
122
+ # @return [Array<Email>]
123
+ def get_emails(token, email)
124
+ auth = token.to_s.strip
125
+ address = email.to_s.strip
126
+ raise "getnada: empty token" if auth.empty?
127
+ raise "getnada: empty email" if address.empty?
128
+
129
+ resp = Http.get("#{API_BASE}/inbox/messages?token=#{URI.encode_www_form_component(auth)}",
130
+ headers: HEADERS_JSON, timeout: 15)
131
+ resp.raise_for_status
132
+ data = resp.json
133
+ rows = data.is_a?(Hash) ? data["messages"] : []
134
+ return [] unless rows.is_a?(Array)
135
+
136
+ rows.filter_map do |item|
137
+ next unless item.is_a?(Hash)
138
+
139
+ message_id = item["id"].to_s.strip
140
+ detail = message_id.empty? ? nil : fetch_detail(auth, message_id)
141
+ Normalize.normalize_email(flatten_message(detail || item, address), address)
142
+ end
143
+ end
144
+ end
145
+ end
146
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TempmailSdk
4
+ module Providers
5
+ # gonebox.email 渠道实现
6
+ # 一次性临时邮箱服务,无需认证
7
+ module GoneboxEmail
8
+ CHANNEL = "gonebox-email"
9
+ BASE_URL = "https://api.gonebox.email/api/v1"
10
+ HEADERS = {
11
+ "User-Agent" => "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " \
12
+ "(KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36",
13
+ "Accept" => "application/json",
14
+ "Content-Type" => "application/json"
15
+ }.freeze
16
+
17
+ module_function
18
+
19
+ # @return [EmailInfo]
20
+ def generate_email
21
+ resp = Http.post(
22
+ "#{BASE_URL}/inboxes",
23
+ headers: HEADERS,
24
+ json: { "domain" => "gonebox.email" },
25
+ timeout: 15
26
+ )
27
+ resp.raise_for_status
28
+ body = resp.json
29
+ raise "gonebox-email: create failed" unless body.is_a?(Hash) && body["success"]
30
+
31
+ data = body["data"]
32
+ raise "gonebox-email: invalid data" unless data.is_a?(Hash)
33
+
34
+ address = data["address"].to_s
35
+ raise "gonebox-email: missing address" if address.empty?
36
+
37
+ # expiresAt 为秒级时间戳,转为毫秒
38
+ expires_at = nil
39
+ raw_expires = data["expiresAt"]
40
+ if raw_expires
41
+ begin
42
+ expires_at = raw_expires.to_i * 1000
43
+ rescue StandardError
44
+ # 忽略
45
+ end
46
+ end
47
+
48
+ EmailInfo.new(channel: CHANNEL, email: address, token: "", expires_at: expires_at)
49
+ end
50
+
51
+ # @param token [String]
52
+ # @param email [String]
53
+ # @return [Array<Email>]
54
+ def get_emails(token, email)
55
+ address = email.to_s.strip
56
+ raise "gonebox-email: missing email" if address.empty?
57
+
58
+ headers = HEADERS.reject { |k, _| k == "Content-Type" }
59
+ resp = Http.get("#{BASE_URL}/inboxes/#{address}/messages", headers: headers, timeout: 15)
60
+ return [] if resp.status_code == 404
61
+
62
+ resp.raise_for_status
63
+ body = resp.json
64
+ return [] unless body.is_a?(Hash) && body["success"]
65
+
66
+ data = body["data"]
67
+ return [] unless data.is_a?(Hash)
68
+
69
+ messages = data["messages"]
70
+ return [] unless messages.is_a?(Array) && !messages.empty?
71
+
72
+ messages.select { |m| m.is_a?(Hash) }.map do |msg|
73
+ raw = {
74
+ "id" => msg["id"].to_s,
75
+ "from" => (msg["from"] || msg["sender"]).to_s,
76
+ "to" => address,
77
+ "subject" => msg["subject"].to_s,
78
+ "text" => (msg["text"] || msg["body_plain"]).to_s,
79
+ "html" => (msg["html"] || msg["body_html"]).to_s,
80
+ "date" => (msg["date"] || msg["received_at"]).to_s,
81
+ "is_read" => false,
82
+ "attachments" => []
83
+ }
84
+ Normalize.normalize_email(raw, address)
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TempmailSdk
4
+ module Providers
5
+ # guerrillamail.com 渠道实现
6
+ # API: https://api.guerrillamail.com/ajax.php
7
+ module Guerrillamail
8
+ CHANNEL = "guerrillamail"
9
+ BASE_URL = "https://api.guerrillamail.com/ajax.php"
10
+ DEFAULT_HEADERS = {
11
+ "User-Agent" => "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
12
+ }.freeze
13
+
14
+ module_function
15
+
16
+ # @return [EmailInfo]
17
+ def generate_email
18
+ resp = Http.get("#{BASE_URL}?f=get_email_address&lang=en", headers: DEFAULT_HEADERS)
19
+ resp.raise_for_status
20
+ data = resp.json
21
+ if data["email_addr"].to_s.empty? || data["sid_token"].to_s.empty?
22
+ raise "Failed to generate email: missing email_addr or sid_token"
23
+ end
24
+
25
+ expires_at = nil
26
+ expires_at = (data["email_timestamp"] + 3600) * 1000 if data["email_timestamp"]
27
+ EmailInfo.new(channel: CHANNEL, email: data["email_addr"], token: data["sid_token"], expires_at: expires_at)
28
+ end
29
+
30
+ # @param email [String]
31
+ # @param token [String]
32
+ # @return [Array<Email>]
33
+ def get_emails(email, token)
34
+ sid = URI.encode_www_form_component(token)
35
+ resp = Http.get("#{BASE_URL}?f=check_email&seq=0&sid_token=#{sid}", headers: DEFAULT_HEADERS)
36
+ resp.raise_for_status
37
+ list = resp.json["list"]
38
+ return [] unless list.is_a?(Array)
39
+
40
+ list.map do |item|
41
+ body = item["mail_body"].to_s
42
+ if body.empty? && item["mail_id"]
43
+ begin
44
+ mid = URI.encode_www_form_component(item["mail_id"].to_s)
45
+ dr = Http.get("#{BASE_URL}?f=fetch_email&sid_token=#{sid}&email_id=#{mid}", headers: DEFAULT_HEADERS)
46
+ body = dr.json["mail_body"].to_s if dr.ok?
47
+ rescue StandardError
48
+ # 忽略单封详情失败
49
+ end
50
+ end
51
+ text = item["mail_excerpt"].to_s
52
+ text = body.gsub(/<[^>]+>/, " ").split.join(" ") if text.empty?
53
+ Normalize.normalize_email({
54
+ "id" => item["mail_id"], "from" => item["mail_from"], "to" => email,
55
+ "subject" => item["mail_subject"], "text" => text, "html" => body,
56
+ "date" => item["mail_date"] || "", "isRead" => item["mail_read"] == 1
57
+ }, email)
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TempmailSdk
4
+ module Providers
5
+ # Guerrillamail 镜像渠道实现
6
+ # sharklasers.com / grr.la / guerrillamail.info / spam4.me 等共用同一套 ajax.php API,仅 baseURL 不同
7
+ module GuerrillamailMirrors
8
+ DEFAULT_HEADERS = {
9
+ "User-Agent" => "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
10
+ }.freeze
11
+
12
+ # 各镜像渠道的固定 baseURL 表
13
+ BASE_URLS = {
14
+ "sharklasers" => "https://www.sharklasers.com/ajax.php",
15
+ "grr-la" => "https://www.grr.la/ajax.php",
16
+ "guerrillamail-info" => "https://www.guerrillamail.info/ajax.php",
17
+ "spam4me" => "https://www.spam4.me/ajax.php",
18
+ "guerrillamail-com" => "https://guerrillamail.com/ajax.php",
19
+ "sharklasers-com" => "https://sharklasers.com/ajax.php",
20
+ "grr-la-com" => "https://grr.la/ajax.php",
21
+ "guerrillamail-net" => "https://www.guerrillamail.net/ajax.php",
22
+ "guerrillamail-org" => "https://www.guerrillamail.org/ajax.php",
23
+ "guerrillamailblock" => "https://www.guerrillamailblock.com/ajax.php",
24
+ "guerrillamail-com-www" => "https://www.guerrillamail.com/ajax.php"
25
+ }.freeze
26
+
27
+ module_function
28
+
29
+ # 创建临时邮箱
30
+ # @param channel [String] 渠道标识
31
+ # @return [EmailInfo]
32
+ def generate_email(channel)
33
+ base_url = BASE_URLS.fetch(channel)
34
+ resp = Http.get("#{base_url}?f=get_email_address&lang=en", headers: DEFAULT_HEADERS)
35
+ resp.raise_for_status
36
+ data = resp.json
37
+ if data["email_addr"].to_s.empty? || data["sid_token"].to_s.empty?
38
+ raise "#{channel}: missing email_addr or sid_token"
39
+ end
40
+
41
+ expires_at = nil
42
+ expires_at = (data["email_timestamp"] + 3600) * 1000 if data["email_timestamp"]
43
+ EmailInfo.new(channel: channel, email: data["email_addr"], token: data["sid_token"], expires_at: expires_at)
44
+ end
45
+
46
+ # 获取邮件列表,对无正文的邮件调用 fetch_email 拉取完整正文
47
+ # @param channel [String] 渠道标识
48
+ # @param email [String]
49
+ # @param token [String]
50
+ # @return [Array<Email>]
51
+ def get_emails(channel, email, token)
52
+ base_url = BASE_URLS.fetch(channel)
53
+ sid = URI.encode_www_form_component(token)
54
+ resp = Http.get("#{base_url}?f=check_email&seq=0&sid_token=#{sid}", headers: DEFAULT_HEADERS)
55
+ resp.raise_for_status
56
+ list = resp.json["list"]
57
+ return [] unless list.is_a?(Array)
58
+
59
+ list.map do |item|
60
+ body = item["mail_body"].to_s
61
+ if body.empty? && item["mail_id"]
62
+ begin
63
+ mid = URI.encode_www_form_component(item["mail_id"].to_s)
64
+ dr = Http.get("#{base_url}?f=fetch_email&sid_token=#{sid}&email_id=#{mid}", headers: DEFAULT_HEADERS)
65
+ body = dr.json["mail_body"].to_s if dr.ok?
66
+ rescue StandardError
67
+ # 忽略单封详情失败
68
+ end
69
+ end
70
+ text = body.empty? ? item["mail_excerpt"].to_s : body.gsub(/<[^>]+>/, " ")
71
+ text = text.split.join(" ")
72
+ Normalize.normalize_email({
73
+ "id" => item["mail_id"], "from" => item["mail_from"], "to" => email,
74
+ "subject" => item["mail_subject"], "text" => text, "html" => body,
75
+ "date" => item["mail_date"] || "", "isRead" => item["mail_read"] == 1
76
+ }, email)
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TempmailSdk
4
+ module Providers
5
+ # harakirimail.com 渠道实现
6
+ # 无需认证、无需 Cookie、无需 Token 的简单 REST API
7
+ module Harakirimail
8
+ CHANNEL = "harakirimail"
9
+ BASE = "https://harakirimail.com"
10
+ HEADERS = {
11
+ "Accept" => "application/json, text/plain, */*",
12
+ "Accept-Language" => "zh-CN,zh;q=0.9,en;q=0.8",
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_name(length = 12)
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
+ name = random_name
27
+ email = "#{name}@harakirimail.com"
28
+
29
+ # 调用收件箱接口验证地址可用
30
+ resp = Http.get("#{BASE}/api/v1/inbox/#{name}", headers: HEADERS, timeout: 15)
31
+ resp.raise_for_status
32
+
33
+ EmailInfo.new(channel: CHANNEL, email: email)
34
+ end
35
+
36
+ # 获取单封邮件正文
37
+ def fetch_body(email_id)
38
+ return {} if email_id.to_s.empty?
39
+
40
+ begin
41
+ resp = Http.get("#{BASE}/api/v1/email/#{email_id}", headers: HEADERS, timeout: 15)
42
+ resp.raise_for_status
43
+ data = resp.json
44
+ data.is_a?(Hash) ? data : {}
45
+ rescue StandardError
46
+ {}
47
+ end
48
+ end
49
+
50
+ # @param email [String]
51
+ # @return [Array<Email>]
52
+ def get_emails(email)
53
+ e = email.to_s.strip
54
+ raise "harakirimail: empty email" if e.empty?
55
+
56
+ name = e.include?("@") ? e.split("@").first.to_s : e
57
+
58
+ resp = Http.get("#{BASE}/api/v1/inbox/#{name}", headers: HEADERS, timeout: 15)
59
+ resp.raise_for_status
60
+ data = resp.json
61
+ return [] unless data.is_a?(Hash)
62
+
63
+ rows = data["emails"]
64
+ return [] unless rows.is_a?(Array)
65
+
66
+ rows.select { |r| r.is_a?(Hash) }.map do |raw|
67
+ email_id = raw["_id"].to_s
68
+ detail = fetch_body(email_id)
69
+ flat = {
70
+ "id" => email_id,
71
+ "from" => raw["from"].to_s,
72
+ "to" => e,
73
+ "subject" => raw["subject"].to_s,
74
+ "date" => raw["received"].to_s,
75
+ "html" => (detail["body_html"] || detail["html"]).to_s,
76
+ "text" => (detail["body_text"] || detail["text"]).to_s,
77
+ "isRead" => false
78
+ }
79
+ Normalize.normalize_email(flat, e)
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end