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,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TempmailSdk
4
+ module Providers
5
+ # temp-mail.io 渠道实现
6
+ # API: https://api.internal.temp-mail.io/api/v3
7
+ module TempMailIo
8
+ CHANNEL = "temp-mail-io"
9
+ BASE_URL = "https://api.internal.temp-mail.io/api/v3"
10
+ PAGE_URL = "https://temp-mail.io/en"
11
+ UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36"
12
+
13
+ @cors_header = nil
14
+
15
+ module_function
16
+
17
+ def fetch_cors_header
18
+ return @cors_header if @cors_header
19
+
20
+ begin
21
+ resp = Http.get(PAGE_URL, headers: { "User-Agent" => UA }, timeout: 15)
22
+ m = resp.body.match(/mobileTestingHeader\s*:\s*"([^"]+)"/)
23
+ @cors_header = m ? m[1] : "1"
24
+ rescue StandardError
25
+ @cors_header = "1"
26
+ end
27
+ @cors_header
28
+ end
29
+
30
+ def headers
31
+ {
32
+ "Content-Type" => "application/json",
33
+ "Application-Name" => "web",
34
+ "Application-Version" => "4.0.0",
35
+ "X-CORS-Header" => fetch_cors_header,
36
+ "User-Agent" => UA,
37
+ "Origin" => "https://temp-mail.io",
38
+ "Referer" => "https://temp-mail.io/"
39
+ }
40
+ end
41
+
42
+ # @return [EmailInfo]
43
+ def generate_email
44
+ resp = Http.post("#{BASE_URL}/email/new", headers: headers,
45
+ json: { "min_name_length" => 10, "max_name_length" => 10 }, timeout: 15)
46
+ resp.raise_for_status
47
+ data = resp.json
48
+ email = data.is_a?(Hash) ? data["email"] : nil
49
+ token = data.is_a?(Hash) ? data["token"] : nil
50
+ raise "temp-mail-io: invalid generate response" if email.to_s.empty? || token.to_s.empty?
51
+
52
+ EmailInfo.new(channel: CHANNEL, email: email, token: token)
53
+ end
54
+
55
+ # @param email [String]
56
+ # @return [Array<Email>]
57
+ def get_emails(email)
58
+ resp = Http.get("#{BASE_URL}/email/#{email}/messages", headers: headers, timeout: 15)
59
+ resp.raise_for_status
60
+ rows = resp.json
61
+ return [] unless rows.is_a?(Array)
62
+
63
+ rows.select { |r| r.is_a?(Hash) }.map { |raw| Normalize.normalize_email(raw, email) }
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TempmailSdk
4
+ module Providers
5
+ # Temp-Mail.Now 渠道 — https://temp-mail.now
6
+ # 基于会话 Cookie 的临时邮箱服务
7
+ module TempMailNow
8
+ CHANNEL = "temp-mail-now"
9
+ BASE_URL = "https://temp-mail.now"
10
+
11
+ PAGE_HEADERS = {
12
+ "Accept" => "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
13
+ "User-Agent" => "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
14
+ }.freeze
15
+
16
+ API_HEADERS = {
17
+ "Accept" => "application/json, text/javascript, */*; q=0.01",
18
+ "X-Requested-With" => "XMLHttpRequest",
19
+ "Referer" => "#{BASE_URL}/en/",
20
+ "User-Agent" => "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
21
+ }.freeze
22
+
23
+ module_function
24
+
25
+ def extract_cookies(resp)
26
+ cookies = {}
27
+ resp.set_cookies.each do |line|
28
+ pair = line.split(";").first.to_s.strip
29
+ k, v = pair.split("=", 2)
30
+ cookies[k] = v if k && v
31
+ end
32
+ cookies
33
+ end
34
+
35
+ def cookies_to_str(cookies)
36
+ cookies.map { |k, v| "#{k}=#{v}" }.join("; ")
37
+ end
38
+
39
+ # 创建临时邮箱
40
+ # @return [EmailInfo]
41
+ def generate_email
42
+ # 获取 session cookie
43
+ resp = Http.get("#{BASE_URL}/en/", headers: PAGE_HEADERS, timeout: 15)
44
+ resp.raise_for_status
45
+ cookies = extract_cookies(resp)
46
+ cookie_str = cookies_to_str(cookies)
47
+ raise "temp-mail-now: 无法获取 session cookie" if cookie_str.empty?
48
+
49
+ # 创建新邮箱
50
+ resp2 = Http.post("#{BASE_URL}/change_email",
51
+ headers: API_HEADERS.merge("Cookie" => cookie_str), timeout: 15)
52
+ resp2.raise_for_status
53
+
54
+ # 合并可能更新的 cookie
55
+ extract_cookies(resp2).each { |k, v| cookies[k] = v }
56
+ cookie_str = cookies_to_str(cookies)
57
+
58
+ data = resp2.json
59
+ raise "temp-mail-now: 响应格式无效" unless data.is_a?(Hash)
60
+
61
+ address = data["new_email"] || ""
62
+ raise "temp-mail-now: 创建邮箱失败" if address.empty? || !address.include?("@")
63
+
64
+ EmailInfo.new(channel: CHANNEL, email: address, token: cookie_str)
65
+ end
66
+
67
+ # 获取邮件列表
68
+ # @param token [String] session cookie
69
+ # @param email [String]
70
+ # @return [Array<Email>]
71
+ def get_emails(token, email)
72
+ raise "temp-mail-now: session cookie 不能为空" if token.nil? || token.empty?
73
+
74
+ addr = (email || "").strip
75
+ raise "temp-mail-now: 邮箱地址不能为空" if addr.empty?
76
+
77
+ resp = Http.get("#{BASE_URL}/fetch_emails",
78
+ headers: API_HEADERS.merge("Cookie" => token), timeout: 15)
79
+ resp.raise_for_status
80
+ data = resp.json
81
+ return [] unless data.is_a?(Hash)
82
+
83
+ emails_list = data["emails"]
84
+ return [] unless emails_list.is_a?(Array)
85
+
86
+ emails_list.filter_map do |item|
87
+ next unless item.is_a?(Hash)
88
+
89
+ raw = {
90
+ "id" => item["id"] || "",
91
+ "from" => item["from"] || item["from_address"] || item["sender"] || "",
92
+ "to" => item["to"] || addr,
93
+ "subject" => item["subject"] || "",
94
+ "text" => item["text"] || item["body_text"] || "",
95
+ "html" => item["html"] || item["body_html"] || "",
96
+ "date" => item["date"] || item["received_at"] || ""
97
+ }
98
+ Normalize.normalize_email(raw, addr)
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "uri"
4
+
5
+ module TempmailSdk
6
+ module Providers
7
+ # temp-mail.org 渠道 — https://temp-mail.org
8
+ # API Base: https://web2.temp-mail.org
9
+ module TempMailOrg
10
+ CHANNEL = "temp-mail-org"
11
+ BASE_URL = "https://web2.temp-mail.org"
12
+
13
+ HEADERS = {
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
+ "Origin" => "https://temp-mail.org",
16
+ "Referer" => "https://temp-mail.org/"
17
+ }.freeze
18
+
19
+ module_function
20
+
21
+ def auth_headers(token)
22
+ HEADERS.merge("Authorization" => "Bearer #{token}")
23
+ end
24
+
25
+ # 创建临时邮箱
26
+ # @return [EmailInfo]
27
+ def generate_email
28
+ resp = Http.post("#{BASE_URL}/mailbox", headers: HEADERS, timeout: 15)
29
+ resp.raise_for_status
30
+ data = resp.json
31
+ raise "temp-mail-org: 响应格式无效" unless data.is_a?(Hash)
32
+
33
+ token = data["token"] || ""
34
+ mailbox = data["mailbox"] || ""
35
+ raise "temp-mail-org: 创建邮箱失败" if token.empty? || mailbox.empty?
36
+
37
+ EmailInfo.new(channel: CHANNEL, email: mailbox, token: token)
38
+ end
39
+
40
+ # 获取邮件列表
41
+ # @param token [String] JWT 令牌
42
+ # @param email [String]
43
+ # @return [Array<Email>]
44
+ def get_emails(token, email)
45
+ raise "temp-mail-org: token 不能为空" if token.nil? || token.empty?
46
+
47
+ addr = (email || "").strip
48
+ raise "temp-mail-org: 邮箱地址不能为空" if addr.empty?
49
+
50
+ resp = Http.get("#{BASE_URL}/messages", headers: auth_headers(token), timeout: 15)
51
+ resp.raise_for_status
52
+ data = resp.json
53
+ return [] unless data.is_a?(Hash)
54
+
55
+ messages = data["messages"]
56
+ return [] unless messages.is_a?(Array) && !messages.empty?
57
+
58
+ messages.filter_map do |msg|
59
+ next unless msg.is_a?(Hash)
60
+
61
+ msg_id = msg["_id"] || ""
62
+ next if msg_id.empty?
63
+
64
+ detail = fetch_message_detail(token, msg_id)
65
+ raw = {
66
+ "id" => msg_id,
67
+ "from" => detail["from"] || msg["from"] || "",
68
+ "to" => addr,
69
+ "subject" => detail["subject"] || msg["subject"] || "",
70
+ "text" => detail["bodyPreview"] || msg["bodyPreview"] || "",
71
+ "html" => detail["bodyHtml"] || "",
72
+ "date" => detail["createdAt"] || msg["receivedAt"].to_s
73
+ }
74
+ Normalize.normalize_email(raw, addr)
75
+ end
76
+ end
77
+
78
+ def fetch_message_detail(token, msg_id)
79
+ resp = Http.get("#{BASE_URL}/messages/#{msg_id}", headers: auth_headers(token), timeout: 15)
80
+ return {} unless resp.ok?
81
+
82
+ data = resp.json
83
+ data.is_a?(Hash) ? data : {}
84
+ rescue StandardError
85
+ {}
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,142 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module TempmailSdk
6
+ module Providers
7
+ # tempemail.co 渠道 — https://tempemail.co
8
+ # Cookie Session + REST JSON API
9
+ module TempemailCo
10
+ CHANNEL = "tempemail-co"
11
+ BASE_URL = "https://tempemail.co"
12
+
13
+ # 从邮件列表 HTML 中提取邮件 ID(data-id="数字")
14
+ MAIL_ID_RE = /data-id="(\d+)"/
15
+
16
+ HEADERS = {
17
+ "Accept" => "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
18
+ "Accept-Language" => "en-US,en;q=0.9",
19
+ "Cache-Control" => "no-cache",
20
+ "DNT" => "1",
21
+ "Pragma" => "no-cache",
22
+ "Upgrade-Insecure-Requests" => "1",
23
+ "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"
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 cookies_to_str(cookies)
39
+ cookies.sort.map { |k, v| "#{k}=#{v}" }.join("; ")
40
+ end
41
+
42
+ def encode_token(address, cookies)
43
+ JSON.generate({ "address" => address, "cookies" => cookies })
44
+ end
45
+
46
+ def decode_token(token)
47
+ data = JSON.parse(token)
48
+ address = data["address"] || ""
49
+ cookies = data["cookies"] || {}
50
+ raise "tempemail-co: token 数据不完整" if address.empty? || !cookies.is_a?(Hash)
51
+
52
+ [address, cookies]
53
+ end
54
+
55
+ # 创建临时邮箱
56
+ # @return [EmailInfo]
57
+ def generate_email
58
+ r = Http.get("#{BASE_URL}/mail/random", headers: HEADERS.merge(
59
+ "Referer" => "#{BASE_URL}/",
60
+ "Accept" => "application/json, text/javascript, */*; q=0.01"
61
+ ))
62
+ r.raise_for_status
63
+ data = r.json
64
+ raise "tempemail-co: 创建邮箱失败" unless data["result"]
65
+
66
+ address = (data["address"] || data["id"] || "").strip
67
+ raise "tempemail-co: 返回的邮箱地址无效" if address.empty? || !address.include?("@")
68
+
69
+ cookies = extract_cookies(r)
70
+ tok = encode_token(address, cookies)
71
+ EmailInfo.new(channel: CHANNEL, email: address, token: tok)
72
+ end
73
+
74
+ # 获取邮件列表
75
+ # @param email [String]
76
+ # @param token [String]
77
+ # @return [Array<Email>]
78
+ def get_emails(email, token)
79
+ raise "tempemail-co: token 不能为空" if token.nil? || token.empty?
80
+
81
+ addr = (email || "").strip
82
+ raise "tempemail-co: 邮箱地址不能为空" if addr.empty?
83
+
84
+ address, cookies = decode_token(token)
85
+ cookie_str = cookies_to_str(cookies)
86
+
87
+ # 获取邮件列表
88
+ r = Http.get("#{BASE_URL}/get-mails?mail_id=#{URI.encode_www_form_component(address)}&unseen=0&is_new=1",
89
+ headers: HEADERS.merge(
90
+ "Referer" => "#{BASE_URL}/",
91
+ "Cookie" => cookie_str,
92
+ "Accept" => "application/json, text/javascript, */*; q=0.01"
93
+ ))
94
+ r.raise_for_status
95
+ data = r.json
96
+ count = data["count"] || 0
97
+ return [] if count <= 0
98
+
99
+ mails_html = data["mails"] || ""
100
+ return [] unless mails_html.is_a?(String) && !mails_html.empty?
101
+
102
+ mail_ids = mails_html.scan(MAIL_ID_RE).flatten.uniq
103
+ return [] if mail_ids.empty?
104
+
105
+ mail_ids.filter_map do |mail_id|
106
+ begin
107
+ detail_resp = Http.get("#{BASE_URL}/mail/info?id=#{mail_id}",
108
+ headers: HEADERS.merge(
109
+ "Referer" => "#{BASE_URL}/",
110
+ "Cookie" => cookie_str,
111
+ "Accept" => "application/json, text/javascript, */*; q=0.01"
112
+ ))
113
+ next unless detail_resp.ok?
114
+
115
+ detail_data = detail_resp.json
116
+ next unless detail_data["result"]
117
+
118
+ mail_info = detail_data["mail"]
119
+ next unless mail_info.is_a?(Hash)
120
+
121
+ from_name = (mail_info["fromName"] || "").strip
122
+ from_addr = (mail_info["fromAddress"] || "").strip
123
+ from_display = from_name.empty? ? from_addr : "#{from_name} <#{from_addr}>"
124
+
125
+ raw = {
126
+ "id" => mail_id.to_s,
127
+ "from" => from_display,
128
+ "from_address" => from_addr,
129
+ "to" => addr,
130
+ "subject" => mail_info["subject"] || "",
131
+ "html" => mail_info["textHtml"] || "",
132
+ "date" => mail_info["displayDate"] || ""
133
+ }
134
+ Normalize.normalize_email(raw, addr)
135
+ rescue StandardError
136
+ next
137
+ end
138
+ end
139
+ end
140
+ end
141
+ end
142
+ end
@@ -0,0 +1,121 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "base64"
4
+ require "json"
5
+
6
+ module TempmailSdk
7
+ module Providers
8
+ # tempemail.info 临时邮箱渠道
9
+ # GET / 获取 PHPSESSID 会话 Cookie + base64 编码的邮箱地址
10
+ # POST /template/checker.php 获取邮件列表
11
+ # GET /view/{date} 获取单封邮件 HTML 正文
12
+ module TempemailInfo
13
+ CHANNEL = "tempemail-info"
14
+ BASE_URL = "https://tempemail.info"
15
+
16
+ # 匹配 var emailEncoded = "base64..."
17
+ EMAIL_RE = /var\s+emailEncoded\s*=\s*"([^"]+)"/
18
+
19
+ UA = "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
+
21
+ module_function
22
+
23
+ def base_headers
24
+ {
25
+ "User-Agent" => UA,
26
+ "Accept-Language" => "zh-CN,zh;q=0.9,en;q=0.8",
27
+ "Referer" => "#{BASE_URL}/",
28
+ "Origin" => BASE_URL
29
+ }
30
+ end
31
+
32
+ def extract_cookie_header(resp)
33
+ cookies = {}
34
+ resp.set_cookies.each do |line|
35
+ pair = line.split(";").first.to_s.strip
36
+ k, v = pair.split("=", 2)
37
+ cookies[k] = v if k && v
38
+ end
39
+ cookies.sort.map { |k, v| "#{k}=#{v}" }.join("; ")
40
+ end
41
+
42
+ # 创建临时邮箱
43
+ # @return [EmailInfo]
44
+ def generate_email
45
+ headers = base_headers.merge("Accept" => "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
46
+ resp = Http.get("#{BASE_URL}/", headers: headers)
47
+ resp.raise_for_status
48
+
49
+ cookie_hdr = extract_cookie_header(resp)
50
+ raise "tempemail-info: 未获取到会话 Cookie" if cookie_hdr.empty?
51
+
52
+ m = resp.body.match(EMAIL_RE)
53
+ raise "tempemail-info: 未在页面中找到 emailEncoded" unless m
54
+
55
+ decoded = Base64.decode64(m[1]).force_encoding("UTF-8").strip
56
+ raise "tempemail-info: 解码出的邮箱地址无效" if decoded.empty? || !decoded.include?("@")
57
+
58
+ EmailInfo.new(channel: CHANNEL, email: decoded, token: cookie_hdr)
59
+ end
60
+
61
+ # 获取邮件列表
62
+ # @param email [String]
63
+ # @param token [String] 会话 Cookie 串
64
+ # @return [Array<Email>]
65
+ def get_emails(email, token)
66
+ cookie_hdr = (token || "").strip
67
+ raise "tempemail-info: 缺少会话 Cookie" if cookie_hdr.empty?
68
+
69
+ headers = base_headers.merge(
70
+ "Accept" => "application/json, text/javascript, */*; q=0.01",
71
+ "X-Requested-With" => "XMLHttpRequest",
72
+ "Content-Type" => "application/x-www-form-urlencoded",
73
+ "Cookie" => cookie_hdr
74
+ )
75
+
76
+ resp = Http.post("#{BASE_URL}/template/checker.php", headers: headers, body: "last_id=0")
77
+ resp.raise_for_status
78
+
79
+ rows = resp.json
80
+ return [] unless rows.is_a?(Array) && !rows.empty?
81
+
82
+ rows.filter_map do |row|
83
+ next unless row.is_a?(Hash)
84
+
85
+ from_addr = (row["from"] || "").to_s
86
+ name = (row["name"] || "").to_s
87
+ from_addr = "#{name} <#{from_addr}>" if !name.empty? && name != from_addr
88
+
89
+ date = (row["date"] || "").to_s
90
+ html_body = fetch_body(cookie_hdr, date)
91
+
92
+ raw = {
93
+ "id" => (row["id"] || "").to_s,
94
+ "from" => from_addr,
95
+ "to" => email,
96
+ "subject" => row["subject"] || "",
97
+ "date" => date,
98
+ "html" => html_body,
99
+ "isRead" => !!row["read"]
100
+ }
101
+ Normalize.normalize_email(raw, email)
102
+ end
103
+ end
104
+
105
+ def fetch_body(cookie_hdr, date)
106
+ return "" if date.nil? || date.to_s.strip.empty?
107
+
108
+ headers = base_headers.merge(
109
+ "Accept" => "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
110
+ "Cookie" => cookie_hdr
111
+ )
112
+ resp = Http.get("#{BASE_URL}/view/#{URI.encode_www_form_component(date)}", headers: headers)
113
+ return "" unless resp.ok?
114
+
115
+ resp.body
116
+ rescue StandardError
117
+ ""
118
+ end
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,159 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module TempmailSdk
6
+ module Providers
7
+ # tempemails.net 渠道 — https://tempemails.net
8
+ # Laravel Cookie Session + CSRF + REST JSON API
9
+ module TempemailsNet
10
+ CHANNEL = "tempemails-net"
11
+ BASE_URL = "https://tempemails.net"
12
+
13
+ # 从 HTML 中提取 CSRF token
14
+ CSRF_RE = /<meta\s+name="csrf-token"\s+content="([^"]+)"/
15
+
16
+ UA = "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"
17
+
18
+ module_function
19
+
20
+ def build_headers(extra = {})
21
+ h = {
22
+ "Accept" => "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
23
+ "Accept-Language" => "en-US,en;q=0.9",
24
+ "Cache-Control" => "no-cache",
25
+ "DNT" => "1",
26
+ "Pragma" => "no-cache",
27
+ "Upgrade-Insecure-Requests" => "1",
28
+ "User-Agent" => UA
29
+ }
30
+ h.merge(extra)
31
+ end
32
+
33
+ def extract_cookies(resp)
34
+ cookies = {}
35
+ resp.set_cookies.each do |line|
36
+ pair = line.split(";").first.to_s.strip
37
+ k, v = pair.split("=", 2)
38
+ cookies[k] = v if k && v
39
+ end
40
+ cookies
41
+ end
42
+
43
+ def cookies_to_str(cookies)
44
+ cookies.sort.map { |k, v| "#{k}=#{v}" }.join("; ")
45
+ end
46
+
47
+ def encode_token(csrf, cookies)
48
+ JSON.generate({ "csrf" => csrf, "cookies" => cookies })
49
+ end
50
+
51
+ def decode_token(token)
52
+ data = JSON.parse(token)
53
+ csrf = data["csrf"] || ""
54
+ cookies = data["cookies"] || {}
55
+ raise "tempemails-net: token 数据不完整" if csrf.empty? || !cookies.is_a?(Hash)
56
+
57
+ [csrf, cookies]
58
+ end
59
+
60
+ # 创建临时邮箱
61
+ # @return [EmailInfo]
62
+ def generate_email
63
+ # 获取首页,建立 session 并提取 CSRF
64
+ r = Http.get("#{BASE_URL}/", headers: build_headers)
65
+ r.raise_for_status
66
+
67
+ m = r.body.match(CSRF_RE)
68
+ raise "tempemails-net: 无法从首页提取 CSRF token" unless m
69
+
70
+ csrf = m[1]
71
+ cookies = extract_cookies(r)
72
+ cookie_str = cookies_to_str(cookies)
73
+
74
+ # POST /get_messages 获取自动分配的邮箱
75
+ r2 = Http.post("#{BASE_URL}/get_messages", headers: build_headers(
76
+ "Accept" => "application/json",
77
+ "X-Requested-With" => "XMLHttpRequest",
78
+ "X-CSRF-TOKEN" => csrf,
79
+ "Cookie" => cookie_str,
80
+ "Referer" => "#{BASE_URL}/"
81
+ ))
82
+ r2.raise_for_status
83
+ data = r2.json
84
+ raise "tempemails-net: 获取邮箱失败" unless data["status"]
85
+
86
+ mailbox = (data["mailbox"] || "").strip
87
+ raise "tempemails-net: 返回的邮箱地址无效" if mailbox.empty? || !mailbox.include?("@")
88
+
89
+ merged_cookies = cookies.merge(extract_cookies(r2))
90
+ tok = encode_token(csrf, merged_cookies)
91
+ EmailInfo.new(channel: CHANNEL, email: mailbox, token: tok)
92
+ end
93
+
94
+ # 获取邮件列表
95
+ # @param email [String]
96
+ # @param token [String]
97
+ # @return [Array<Email>]
98
+ def get_emails(email, token)
99
+ raise "tempemails-net: token 不能为空" if token.nil? || token.empty?
100
+
101
+ addr = (email || "").strip
102
+ raise "tempemails-net: 邮箱地址不能为空" if addr.empty?
103
+
104
+ csrf, cookies = decode_token(token)
105
+ cookie_str = cookies_to_str(cookies)
106
+
107
+ r = Http.post("#{BASE_URL}/get_messages", headers: build_headers(
108
+ "Accept" => "application/json",
109
+ "X-Requested-With" => "XMLHttpRequest",
110
+ "X-CSRF-TOKEN" => csrf,
111
+ "Cookie" => cookie_str,
112
+ "Referer" => "#{BASE_URL}/"
113
+ ))
114
+ r.raise_for_status
115
+ data = r.json
116
+
117
+ messages = data["messages"]
118
+ return [] unless messages.is_a?(Array) && !messages.empty?
119
+
120
+ messages.filter_map do |msg|
121
+ next unless msg.is_a?(Hash)
122
+
123
+ mail_id = msg["id"] || ""
124
+ html_body = ""
125
+ unless mail_id.to_s.empty?
126
+ begin
127
+ view_resp = Http.get("#{BASE_URL}/view/#{mail_id}", headers: build_headers(
128
+ "Cookie" => cookie_str,
129
+ "Referer" => "#{BASE_URL}/"
130
+ ))
131
+ html_body = view_resp.body if view_resp.ok?
132
+ rescue StandardError
133
+ # 单封邮件正文获取失败不影响其他
134
+ end
135
+ end
136
+
137
+ from_name = (msg["from"] || "").strip
138
+ from_addr = (msg["from_email"] || "").strip
139
+ from_display = if !from_name.empty? && !from_addr.empty?
140
+ "#{from_name} <#{from_addr}>"
141
+ else
142
+ from_addr.empty? ? from_name : from_addr
143
+ end
144
+
145
+ raw = {
146
+ "id" => mail_id.to_s,
147
+ "from" => from_display,
148
+ "from_address" => from_addr,
149
+ "to" => addr,
150
+ "subject" => msg["subject"] || "",
151
+ "html" => html_body,
152
+ "date" => msg["receivedAt"] || ""
153
+ }
154
+ Normalize.normalize_email(raw, addr)
155
+ end
156
+ end
157
+ end
158
+ end
159
+ end