tempmail_sdk 1.3.3 → 1.3.5

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 (73) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/lib/tempmail_sdk/channels.rb +29 -7
  4. data/lib/tempmail_sdk/channels_data.rb +29 -7
  5. data/lib/tempmail_sdk/providers/altmails.rb +99 -0
  6. data/lib/tempmail_sdk/providers/apihz.rb +138 -0
  7. data/lib/tempmail_sdk/providers/awamail.rb +95 -0
  8. data/lib/tempmail_sdk/providers/best_temp_mail.rb +77 -0
  9. data/lib/tempmail_sdk/providers/chatgpt_org_uk.rb +129 -0
  10. data/lib/tempmail_sdk/providers/clawdemail.rb +122 -0
  11. data/lib/tempmail_sdk/providers/crazymailing.rb +114 -0
  12. data/lib/tempmail_sdk/providers/disposablemail.rb +125 -0
  13. data/lib/tempmail_sdk/providers/disposablemail_app.rb +66 -0
  14. data/lib/tempmail_sdk/providers/email10min.rb +152 -0
  15. data/lib/tempmail_sdk/providers/emailnator.rb +132 -0
  16. data/lib/tempmail_sdk/providers/emailtemp_org.rb +131 -0
  17. data/lib/tempmail_sdk/providers/expressinboxhub.rb +118 -0
  18. data/lib/tempmail_sdk/providers/fakemail.rb +143 -0
  19. data/lib/tempmail_sdk/providers/firetempmail.rb +86 -0
  20. data/lib/tempmail_sdk/providers/flybymail.rb +93 -0
  21. data/lib/tempmail_sdk/providers/generator_email.rb +146 -0
  22. data/lib/tempmail_sdk/providers/haribu.rb +29 -3
  23. data/lib/tempmail_sdk/providers/huskmail.rb +132 -0
  24. data/lib/tempmail_sdk/providers/internxt.rb +176 -0
  25. data/lib/tempmail_sdk/providers/linshi_xyz.rb +79 -0
  26. data/lib/tempmail_sdk/providers/linshiyouxiang_net.rb +85 -0
  27. data/lib/tempmail_sdk/providers/mail_sunls.rb +42 -1
  28. data/lib/tempmail_sdk/providers/mail_td.rb +161 -0
  29. data/lib/tempmail_sdk/providers/mailcat_ai.rb +52 -0
  30. data/lib/tempmail_sdk/providers/maildrop.rb +79 -4
  31. data/lib/tempmail_sdk/providers/mailgolem.rb +96 -0
  32. data/lib/tempmail_sdk/providers/mailinator.rb +9 -2
  33. data/lib/tempmail_sdk/providers/mailmomy.rb +1 -1
  34. data/lib/tempmail_sdk/providers/mailtemp_cc.rb +91 -0
  35. data/lib/tempmail_sdk/providers/mailticking.rb +139 -0
  36. data/lib/tempmail_sdk/providers/minuteinbox.rb +174 -0
  37. data/lib/tempmail_sdk/providers/moakt.rb +29 -3
  38. data/lib/tempmail_sdk/providers/mtempmail.rb +140 -0
  39. data/lib/tempmail_sdk/providers/mytempmail_cc.rb +76 -0
  40. data/lib/tempmail_sdk/providers/nowtempmail.rb +112 -0
  41. data/lib/tempmail_sdk/providers/noxen_de5_net.rb +392 -0
  42. data/lib/tempmail_sdk/providers/nukemail.rb +188 -0
  43. data/lib/tempmail_sdk/providers/nullmail.rb +95 -0
  44. data/lib/tempmail_sdk/providers/openinbox.rb +102 -0
  45. data/lib/tempmail_sdk/providers/shadowmail.rb +174 -0
  46. data/lib/tempmail_sdk/providers/shitpost_email.rb +85 -0
  47. data/lib/tempmail_sdk/providers/smail_pw.rb +156 -0
  48. data/lib/tempmail_sdk/providers/smails.rb +128 -0
  49. data/lib/tempmail_sdk/providers/socketio_mail.rb +25 -0
  50. data/lib/tempmail_sdk/providers/temp_mail_gg.rb +362 -0
  51. data/lib/tempmail_sdk/providers/tempgbox.rb +118 -0
  52. data/lib/tempmail_sdk/providers/tempmail100.rb +148 -0
  53. data/lib/tempmail_sdk/providers/tempmail_cn.rb +111 -0
  54. data/lib/tempmail_sdk/providers/tempmail_ee.rb +373 -0
  55. data/lib/tempmail_sdk/providers/tempmailportal.rb +131 -0
  56. data/lib/tempmail_sdk/providers/tempmails_io.rb +89 -0
  57. data/lib/tempmail_sdk/providers/tempmailto.rb +312 -0
  58. data/lib/tempmail_sdk/providers/temporarymail_com.rb +184 -0
  59. data/lib/tempmail_sdk/providers/tenmin_app.rb +100 -0
  60. data/lib/tempmail_sdk/providers/tenminutemail_net.rb +183 -0
  61. data/lib/tempmail_sdk/providers/thirty_min_email.rb +90 -0
  62. data/lib/tempmail_sdk/providers/tmpkit.rb +129 -0
  63. data/lib/tempmail_sdk/providers/twentyfourmail_chacuo.rb +83 -0
  64. data/lib/tempmail_sdk/providers/vip_215.rb +171 -0
  65. data/lib/tempmail_sdk/providers/zerodrop.rb +90 -0
  66. data/lib/tempmail_sdk/registry.rb +285 -23
  67. data/lib/tempmail_sdk/types.rb +13 -0
  68. data/lib/tempmail_sdk/version.rb +1 -1
  69. data/lib/tempmail_sdk.rb +1 -1
  70. metadata +58 -6
  71. data/lib/tempmail_sdk/providers/gonebox_email.rb +0 -89
  72. data/lib/tempmail_sdk/providers/temp_mail_now.rb +0 -103
  73. data/lib/tempmail_sdk/providers/webmailtemp.rb +0 -108
@@ -0,0 +1,129 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "uri"
5
+
6
+ module TempmailSdk
7
+ module Providers
8
+ # chatgpt.org.uk 渠道实现
9
+ #
10
+ # 流程:
11
+ # 1. GET /api/domains/public 获取可用域名列表(过滤 is_active=1)
12
+ # 2. 本地生成随机用户名,拼接邮箱地址
13
+ # 3. POST /api/inbox-token {"email":"..."} 创建收件箱,返回 inbox JWT 和 gm_sid cookie
14
+ # 4. GET /api/emails?email=... 携带 x-inbox-token 和 gm_sid cookie 获取邮件
15
+ # token 格式: JSON {"gmSid":"...","inbox":"..."}
16
+ module ChatgptOrgUk
17
+ CHANNEL = "chatgpt-org-uk"
18
+ API_BASE = "https://mail.chatgpt.org.uk/api"
19
+ USERNAME_CHARS = ("a".."z").to_a + ("0".."9").to_a
20
+
21
+ HEADERS = {
22
+ "Accept" => "*/*",
23
+ "DNT" => "1",
24
+ "Origin" => "https://mail.chatgpt.org.uk",
25
+ "Referer" => "https://mail.chatgpt.org.uk/zh/",
26
+ "sec-fetch-dest" => "empty",
27
+ "sec-fetch-mode" => "cors",
28
+ "sec-fetch-site" => "same-origin",
29
+ "User-Agent" => "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " \
30
+ "(KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"
31
+ }.freeze
32
+
33
+ module_function
34
+
35
+ # 生成随机用户名
36
+ def random_username(length = 10)
37
+ Array.new(length) { USERNAME_CHARS.sample }.join
38
+ end
39
+
40
+ # 获取可用域名列表
41
+ def fetch_domains
42
+ resp = Http.get("#{API_BASE}/domains/public", headers: HEADERS)
43
+ resp.raise_for_status
44
+ data = resp.json
45
+ raise "chatgpt-org-uk: 获取域名失败" unless data["success"]
46
+
47
+ domains = data.dig("data", "domains") || []
48
+ active = domains.select { |d| d["is_active"] == 1 && !d["domain_name"].to_s.empty? }
49
+ .map { |d| d["domain_name"] }
50
+ raise "chatgpt-org-uk: 无可用域名" if active.empty?
51
+
52
+ active
53
+ end
54
+
55
+ # 创建收件箱,返回 [inbox_token, gm_sid]
56
+ def create_inbox(email)
57
+ resp = Http.post("#{API_BASE}/inbox-token",
58
+ headers: HEADERS.merge("Content-Type" => "application/json"),
59
+ json: { "email" => email })
60
+ resp.raise_for_status
61
+ gm_sid = resp.cookie_value("gm_sid").to_s
62
+ data = resp.json
63
+ raise "chatgpt-org-uk: inbox-token 响应缺少 token" \
64
+ unless data["success"] && !data.dig("auth", "token").to_s.empty?
65
+
66
+ [data.dig("auth", "token"), gm_sid]
67
+ end
68
+
69
+ # 创建临时邮箱
70
+ # @return [EmailInfo]
71
+ def generate_email
72
+ domains = fetch_domains
73
+ domain = domains.sample
74
+ email = "#{random_username}@#{domain}"
75
+ inbox, gm_sid = create_inbox(email)
76
+ packed = JSON.generate("gmSid" => gm_sid, "inbox" => inbox)
77
+ EmailInfo.new(channel: CHANNEL, email: email, token: packed)
78
+ end
79
+
80
+ # 获取邮件列表
81
+ # @param email [String]
82
+ # @param token [String] JSON {"gmSid":"...","inbox":"..."}
83
+ # @return [Array<Email>]
84
+ def get_emails(email, token)
85
+ parsed = JSON.parse(token)
86
+ gm_sid = parsed["gmSid"].to_s
87
+ inbox = parsed["inbox"].to_s
88
+ raise "chatgpt-org-uk: inbox token 缺失" if inbox.empty?
89
+
90
+ # gm_sid 丢失时重新创建 session
91
+ if gm_sid.empty?
92
+ inbox, gm_sid = create_inbox(email)
93
+ end
94
+
95
+ fetch_emails_with(email, inbox, gm_sid)
96
+ rescue JSON::ParserError
97
+ raise "chatgpt-org-uk: 无效的 token 格式"
98
+ end
99
+
100
+ # 实际拉取邮件,401/403 时重建 session 重试一次
101
+ def fetch_emails_with(email, inbox, gm_sid)
102
+ encoded = URI.encode_www_form_component(email)
103
+ resp = Http.get("#{API_BASE}/emails?email=#{encoded}",
104
+ headers: HEADERS.merge(
105
+ "Cookie" => "gm_sid=#{gm_sid}",
106
+ "x-inbox-token" => inbox
107
+ ))
108
+ if [401, 403].include?(resp.status_code)
109
+ new_inbox, new_sid = create_inbox(email)
110
+ resp = Http.get("#{API_BASE}/emails?email=#{encoded}",
111
+ headers: HEADERS.merge(
112
+ "Cookie" => "gm_sid=#{new_sid}",
113
+ "x-inbox-token" => new_inbox
114
+ ))
115
+ end
116
+ resp.raise_for_status
117
+ data = resp.json
118
+ raise "chatgpt-org-uk: 获取邮件失败" unless data["success"]
119
+
120
+ emails_raw = data.dig("data", "emails") || []
121
+ emails_raw.filter_map do |raw|
122
+ next unless raw.is_a?(Hash)
123
+
124
+ Normalize.normalize_email(raw, email)
125
+ end
126
+ end
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,122 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "uri"
4
+
5
+ module TempmailSdk
6
+ module Providers
7
+ # ClawdEmail 渠道实现(clawdemail.com,API 域 api.clawdemail.com)
8
+ #
9
+ # POST /register 建箱(无验证码,body {"name":"xxx"},响应 success/email/token),
10
+ # GET /inbox 读信列表(Header Authorization: Bearer <token>,
11
+ # 响应 success/email/count/unread/emails[]),GET /email/{id} 取单封详情(Bearer)。
12
+ # 信件保留 30 分钟,仅接收不发送,正文仅纯文本。
13
+ module Clawdemail
14
+ CHANNEL = "clawdemail"
15
+ BASE_URL = "https://api.clawdemail.com"
16
+
17
+ HEADERS = {
18
+ "Accept" => "application/json",
19
+ "User-Agent" => "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " \
20
+ "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36"
21
+ }.freeze
22
+
23
+ RETRY_KEYS = %w[id Id slug messageId message_id].freeze
24
+
25
+ module_function
26
+
27
+ # 设置 clawdemail 请求的通用请求头(可选附带 Bearer token)
28
+ # @param token [String] 认证令牌
29
+ # @return [Hash]
30
+ def auth_headers(token)
31
+ hdrs = HEADERS.dup
32
+ hdrs["Authorization"] = "Bearer #{token}" unless token.to_s.empty?
33
+ hdrs
34
+ end
35
+
36
+ # 创建 clawdemail.com 临时邮箱
37
+ # POST /register(body {"name":""})返回 email 与 token。
38
+ # @return [EmailInfo]
39
+ def generate_email
40
+ resp = Http.post("#{BASE_URL}/register",
41
+ headers: auth_headers("").merge("Content-Type" => "application/json"),
42
+ json: { "name" => "" }, timeout: 15)
43
+ raise "clawdemail: 创建邮箱失败 http #{resp.status_code}" unless resp.ok?
44
+
45
+ data = resp.json
46
+ raise "clawdemail: 创建响应非对象" unless data.is_a?(Hash)
47
+
48
+ email = data["email"].to_s.strip
49
+ token = data["token"].to_s.strip
50
+ raise "clawdemail: 创建邮箱响应缺少必要字段" if email.empty? || token.empty? || !email.include?("@")
51
+
52
+ EmailInfo.new(channel: CHANNEL, email: email, token: token)
53
+ end
54
+
55
+ # 获取 clawdemail.com 邮件列表
56
+ # 流程:GET /inbox?limit=50 取列表,对每个元素按 id 逐封 GET /email/{id} 合并详情;
57
+ # 详情失败时以列表摘要归一。
58
+ # @param email [String] 邮箱地址
59
+ # @param token [String] 注册返回的认证令牌
60
+ # @return [Array<Email>]
61
+ def get_emails(email, token)
62
+ addr = email.to_s.strip
63
+ resp = Http.get("#{BASE_URL}/inbox?limit=50",
64
+ headers: auth_headers(token), timeout: 15)
65
+ raise "clawdemail: 获取邮件列表失败 http #{resp.status_code}" unless resp.ok?
66
+
67
+ data = resp.json
68
+ raise "clawdemail: 收件箱响应非对象" unless data.is_a?(Hash)
69
+
70
+ unless data["success"] == true
71
+ raise "clawdemail: 读取收件箱失败: #{data["error"]}"
72
+ end
73
+ emails = data["emails"]
74
+ return [] unless emails.is_a?(Array)
75
+
76
+ emails.filter_map do |m|
77
+ next unless m.is_a?(Hash)
78
+
79
+ detail = get_detail(token, message_id_of(m))
80
+ row = m.dup
81
+ if detail.is_a?(Hash)
82
+ detail.each { |k, v| row[k] = v unless row.key?(k) }
83
+ end
84
+ Normalize.normalize_email(row, addr)
85
+ end
86
+ end
87
+
88
+ # 从列表元素中提取邮件 ID(候选键 id/Id/slug/messageId/message_id)
89
+ # @param row [Hash]
90
+ # @return [String]
91
+ def message_id_of(row)
92
+ RETRY_KEYS.each do |key|
93
+ val = row[key]
94
+ return val.to_s.strip unless val.nil? || val.to_s.strip.empty?
95
+ end
96
+ ""
97
+ end
98
+
99
+ # 获取 clawdemail.com 单封邮件详情
100
+ # GET /email/{id}(Bearer token),响应含 email 嵌套对象(from_addr/body_text/received_at)
101
+ # 时提升嵌套对象。
102
+ # @param token [String] 认证令牌
103
+ # @param id [String] 邮件 ID
104
+ # @return [Hash, nil]
105
+ def get_detail(token, id)
106
+ return nil if id.to_s.empty?
107
+
108
+ uri = "#{BASE_URL}/email/#{URI.encode_www_form_component(id)}"
109
+ resp = Http.get(uri, headers: auth_headers(token), timeout: 15)
110
+ return nil unless resp.ok?
111
+
112
+ detail = resp.json
113
+ return nil unless detail.is_a?(Hash)
114
+
115
+ nested = detail["email"]
116
+ nested.is_a?(Hash) ? nested : detail
117
+ rescue StandardError
118
+ nil
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "uri"
4
+
5
+ module TempmailSdk
6
+ module Providers
7
+ # Crazymailing 渠道实现(crazymailing.com)
8
+ #
9
+ # Next.js 全栈站点,API 结构:
10
+ # POST /api/mailbox(空 JSON body)建箱,响应
11
+ # {"mailbox":{"id":"...","address":"...@crazymailing.com","expiresAt":"<RFC3339>"}};
12
+ # GET /api/messages?mailbox=<完整地址 URL 编码> 读信,响应 {"messages":[...]};
13
+ # GET /api/message/{id}/body 取单封正文(完整 HTML 页面)。
14
+ # 请求需携带 Origin/Referer 浏览器形态头。
15
+ module Crazymailing
16
+ CHANNEL = "crazymailing"
17
+ BASE_URL = "https://crazymailing.com"
18
+
19
+ HEADERS = {
20
+ "Content-Type" => "application/json",
21
+ "Accept" => "application/json",
22
+ "Origin" => BASE_URL,
23
+ "Referer" => "#{BASE_URL}/",
24
+ "User-Agent" => "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " \
25
+ "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36"
26
+ }.freeze
27
+
28
+ RETRY_KEYS = %w[id Id slug messageId message_id].freeze
29
+
30
+ module_function
31
+
32
+ # 创建临时邮箱
33
+ # POST /api/mailbox(空 JSON body);域名由服务端统一分配(当前仅 @crazymailing.com)。
34
+ # @return [EmailInfo]
35
+ def generate_email
36
+ resp = Http.post("#{BASE_URL}/api/mailbox",
37
+ headers: HEADERS, json: {}, timeout: 15)
38
+ raise "crazymailing: 创建邮箱失败 http #{resp.status_code}" unless resp.ok?
39
+
40
+ data = resp.json
41
+ mailbox = data.is_a?(Hash) ? data["mailbox"] : nil
42
+ raise "crazymailing: 创建响应缺少 mailbox" unless mailbox.is_a?(Hash)
43
+
44
+ address = mailbox["address"].to_s.strip
45
+ raise "crazymailing: 创建响应缺少 mailbox.address" if address.empty?
46
+
47
+ EmailInfo.new(channel: CHANNEL, email: address,
48
+ token: mailbox["id"].to_s, expires_at: mailbox["expiresAt"].to_s)
49
+ end
50
+
51
+ # 读取收件箱
52
+ # GET /api/messages?mailbox=<完整地址 URL 编码>,响应 {"messages":[...]};
53
+ # 对每个元素逐封 GET /api/message/{id}/body 拉取正文(失败不阻断列表)。
54
+ # @param email [String] 完整邮箱地址
55
+ # @param token [String] 建箱返回的 mailbox id
56
+ # @return [Array<Email>]
57
+ def get_emails(email, token)
58
+ addr = email.to_s.strip
59
+ raise "crazymailing: 邮箱地址为空" if addr.empty?
60
+
61
+ uri = "#{BASE_URL}/api/messages?mailbox=#{URI.encode_www_form_component(addr)}"
62
+ resp = Http.get(uri, headers: HEADERS, timeout: 15)
63
+ raise "crazymailing: 读取收件箱失败 http #{resp.status_code}" unless resp.ok?
64
+
65
+ data = resp.json
66
+ messages = data.is_a?(Hash) ? data["messages"] : nil
67
+ return [] unless messages.is_a?(Array)
68
+
69
+ messages.filter_map do |m|
70
+ next unless m.is_a?(Hash)
71
+
72
+ row = m.dup
73
+ row["to"] = addr unless row.key?("to")
74
+ id = message_id_of(row)
75
+ unless id.empty?
76
+ # 列表元素为摘要,正文须逐封二拉(失败不阻断)
77
+ html = get_body(id)
78
+ row["html"] = html if !html.empty?
79
+ end
80
+ Normalize.normalize_email(row, addr)
81
+ end
82
+ end
83
+
84
+ # 从列表元素中提取邮件 ID(候选键 id/Id/slug/messageId/message_id)
85
+ # @param row [Hash]
86
+ # @return [String]
87
+ def message_id_of(row)
88
+ RETRY_KEYS.each do |key|
89
+ val = row[key]
90
+ return val.to_s.strip unless val.nil? || val.to_s.strip.empty?
91
+ end
92
+ ""
93
+ end
94
+
95
+ # 拉取单封正文(GET /api/message/{id}/body,响应为完整 HTML 页面)
96
+ # @param id [String] 邮件 ID
97
+ # @return [String]
98
+ def get_body(id)
99
+ uri = "#{BASE_URL}/api/message/#{URI.encode_www_form_component(id)}/body"
100
+ resp = Http.get(uri,
101
+ headers: {
102
+ "Accept" => "text/html,application/xhtml+xml,*/*;q=0.8",
103
+ "Origin" => BASE_URL,
104
+ "Referer" => "#{BASE_URL}/",
105
+ "User-Agent" => HEADERS["User-Agent"]
106
+ },
107
+ timeout: 15)
108
+ return "" unless resp.ok?
109
+
110
+ resp.body.to_s
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,125 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module TempmailSdk
6
+ module Providers
7
+ # disposablemail.com 渠道实现
8
+ #
9
+ # 流程:
10
+ # 1. GET / 获取 PHPSESSID cookie + HTML 中的 CSRF token(CSRF="xxx")
11
+ # 2. GET /index/index?csrf_token={csrf} 创建邮箱,返回 {"email":"user@domain.com"}
12
+ # 3. GET /index/refresh 携带 session cookie 获取邮件列表(捷克语字段: predmet/od/kdy/precteno)
13
+ # 4. GET /email/id/{id} 获取邮件正文 HTML
14
+ # token 存储 "PHPSESSID=xxx" cookie 字符串。
15
+ module Disposablemail
16
+ CHANNEL = "disposablemail"
17
+ BASE_URL = "https://www.disposablemail.com"
18
+
19
+ CSRF_RE = /CSRF="([^"]+)"/.freeze
20
+
21
+ BROWSER_HEADERS = {
22
+ "Accept" => "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8",
23
+ "Accept-Language" => "en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7",
24
+ "User-Agent" => "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " \
25
+ "(KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"
26
+ }.freeze
27
+
28
+ AJAX_HEADERS = {
29
+ "Accept" => "application/json, text/javascript, */*; q=0.01",
30
+ "Accept-Language" => "en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7",
31
+ "Referer" => "#{BASE_URL}/",
32
+ "User-Agent" => "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " \
33
+ "(KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36",
34
+ "X-Requested-With" => "XMLHttpRequest"
35
+ }.freeze
36
+
37
+ module_function
38
+
39
+ # 提取 Set-Cookie 中的 PHPSESSID
40
+ def extract_session(resp)
41
+ resp.set_cookies.each do |line|
42
+ first = line.split(";", 2).first.to_s.strip
43
+ k, v = first.split("=", 2)
44
+ return "#{k}=#{v}" if k == "PHPSESSID"
45
+ end
46
+ ""
47
+ end
48
+
49
+ # 创建临时邮箱
50
+ # @return [EmailInfo]
51
+ def generate_email
52
+ r1 = Http.get("#{BASE_URL}/", headers: BROWSER_HEADERS)
53
+ r1.raise_for_status
54
+ m = r1.body.match(CSRF_RE)
55
+ raise "disposablemail: 未能从首页提取 CSRF token" unless m
56
+
57
+ csrf = m[1]
58
+ session_cookie = extract_session(r1)
59
+
60
+ r2 = Http.get("#{BASE_URL}/index/index?csrf_token=#{csrf}",
61
+ headers: AJAX_HEADERS.merge(session_cookie.empty? ? {} : { "Cookie" => session_cookie }))
62
+ r2.raise_for_status
63
+
64
+ # 若 index 阶段刷新了 PHPSESSID,改用新的
65
+ new_sess = extract_session(r2)
66
+ session_cookie = new_sess unless new_sess.empty?
67
+
68
+ data = JSON.parse(r2.body)
69
+ email = data["email"].to_s.strip
70
+ raise "disposablemail: 邮箱地址无效: #{email.inspect}" if email.empty? || !email.include?("@")
71
+
72
+ EmailInfo.new(channel: CHANNEL, email: email, token: session_cookie)
73
+ end
74
+
75
+ # 获取邮件列表
76
+ # @param email [String]
77
+ # @param token [String] PHPSESSID cookie 字符串
78
+ # @return [Array<Email>]
79
+ def get_emails(email, token)
80
+ raise "disposablemail: 邮箱地址为空" if email.to_s.strip.empty?
81
+
82
+ headers = AJAX_HEADERS.dup
83
+ headers = headers.merge("Cookie" => token) unless token.to_s.empty?
84
+
85
+ resp = Http.get("#{BASE_URL}/index/refresh", headers: headers)
86
+ resp.raise_for_status
87
+ trimmed = resp.body.strip
88
+ return [] if trimmed == "0" || trimmed.empty? || trimmed == "[]"
89
+
90
+ list = begin
91
+ JSON.parse(trimmed)
92
+ rescue JSON::ParserError
93
+ []
94
+ end
95
+ return [] unless list.is_a?(Array) && !list.empty?
96
+
97
+ list.map do |item|
98
+ mail_id = item["id"].to_s
99
+ html_body = fetch_body(mail_id, token)
100
+ Normalize.normalize_email({
101
+ "id" => mail_id,
102
+ "from" => item["od"].to_s,
103
+ "to" => email,
104
+ "subject" => item["predmet"].to_s,
105
+ "html" => html_body,
106
+ "date" => item["kdy"].to_s,
107
+ "isRead" => item["precteno"] == "precteno"
108
+ }, email)
109
+ end
110
+ end
111
+
112
+ # 获取单封邮件 HTML 正文
113
+ def fetch_body(mail_id, token)
114
+ return "" if mail_id.empty?
115
+
116
+ headers = AJAX_HEADERS.dup
117
+ headers = headers.merge("Cookie" => token) unless token.to_s.empty?
118
+ resp = Http.get("#{BASE_URL}/email/id/#{mail_id}", headers: headers)
119
+ resp.ok? ? resp.body : ""
120
+ rescue StandardError
121
+ ""
122
+ end
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module TempmailSdk
6
+ module Providers
7
+ # disposablemail.app 渠道实现
8
+ #
9
+ # 纯 REST JSON API,无需认证。
10
+ # 创建收件箱: POST /api/inbox (body: {})
11
+ # 获取邮件: GET /api/inbox/emails?token={token}
12
+ module DisposablemailApp
13
+ CHANNEL = "disposablemail-app"
14
+ API_BASE = "https://disposablemail.app/api"
15
+
16
+ HEADERS = {
17
+ "Accept" => "application/json, text/plain, */*",
18
+ "Accept-Language" => "zh-CN,zh;q=0.9,en;q=0.8",
19
+ "Content-Type" => "application/json",
20
+ "Origin" => "https://disposablemail.app",
21
+ "Referer" => "https://disposablemail.app/",
22
+ "User-Agent" => "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " \
23
+ "(KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"
24
+ }.freeze
25
+
26
+ module_function
27
+
28
+ # 创建临时邮箱
29
+ # @return [EmailInfo]
30
+ def generate_email
31
+ resp = Http.post("#{API_BASE}/inbox", headers: HEADERS, body: "{}")
32
+ resp.raise_for_status
33
+ data = resp.json
34
+ address = data["address"].to_s.strip
35
+ token = data["token"].to_s.strip
36
+ raise "disposablemail-app: address 或 token 为空" if address.empty? || token.empty?
37
+
38
+ info = EmailInfo.new(channel: CHANNEL, email: address, token: token)
39
+ info.expires_at = data["expiresAt"].to_s
40
+ info.created_at = data["createdAt"].to_s
41
+ info
42
+ end
43
+
44
+ # 获取邮件列表
45
+ # @param email [String]
46
+ # @param token [String] API 返回的 token
47
+ # @return [Array<Email>]
48
+ def get_emails(email, token)
49
+ raise "disposablemail-app: token 为空" if token.to_s.empty?
50
+
51
+ get_headers = HEADERS.reject { |k, _| k == "Content-Type" }
52
+ resp = Http.get("#{API_BASE}/inbox/emails?token=#{token}", headers: get_headers)
53
+ resp.raise_for_status
54
+ data = resp.json
55
+ emails_raw = data["emails"]
56
+ return [] unless emails_raw.is_a?(Array) && !emails_raw.empty?
57
+
58
+ emails_raw.filter_map do |raw|
59
+ next unless raw.is_a?(Hash)
60
+
61
+ Normalize.normalize_email(raw, email)
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end