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,176 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "base64"
4
+ require "cgi"
5
+ require "json"
6
+
7
+ module TempmailSdk
8
+ module Providers
9
+ # Haribu 渠道 — https://haribu.net
10
+ # Tempail 类模式,session cookies 维持会话
11
+ module Haribu
12
+ CHANNEL = "haribu"
13
+ BASE = "https://haribu.net"
14
+ TOK_PREFIX = "haribu1:"
15
+
16
+ HEADERS = {
17
+ "Accept" => "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
18
+ "Accept-Language" => "zh-CN,zh;q=0.9,en;q=0.8",
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 " \
24
+ "(KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36 Edg/146.0.0.0"
25
+ }.freeze
26
+
27
+ # 匹配 <input id="eposta_adres" value="xxx@yyy.com">
28
+ EMAIL_INPUT_RE = /<input[^>]+id\s*=\s*["']eposta_adres["'][^>]+value\s*=\s*["']([^"']+)["']/i
29
+ EMAIL_INPUT_RE2 = /<input[^>]+value\s*=\s*["']([^"']+@[^"']+)["'][^>]+id\s*=\s*["']eposta_adres["']/i
30
+ MAIL_ITEM_RE = /<li\s+class\s*=\s*["']mail["'][^>]*>([\s\S]*?)<\/li>/mi
31
+ FROM_RE = /<span\s+class\s*=\s*["']mail_gonderen["'][^>]*>([\s\S]*?)<\/span>/mi
32
+ SUBJECT_RE = /<span\s+class\s*=\s*["']mail_konu["'][^>]*>([\s\S]*?)<\/span>/mi
33
+ DATE_RE = /<span\s+class\s*=\s*["']mail_zaman["'][^>]*>([\s\S]*?)<\/span>/mi
34
+ MAIL_LINK_RE = /href\s*=\s*["']([^"']*(?:mail|read|view)[^"']*)["']/i
35
+ BODY_RE = /<div\s+(?:id|class)\s*=\s*["'](?:mail_icerik|icerik|mail-content|message-body)["'][^>]*>([\s\S]*?)<\/div>/mi
36
+ TAG_RE = /<[^>]+>/
37
+
38
+ module_function
39
+
40
+ def strip_tags(html_str)
41
+ html_str.gsub(TAG_RE, " ").strip
42
+ end
43
+
44
+ # 从响应中合并 cookies
45
+ def merge_cookies(prev, resp)
46
+ existing = {}
47
+ prev.to_s.split(";").each do |part|
48
+ part = part.strip
49
+ next unless part.include?("=")
50
+
51
+ k, v = part.split("=", 2)
52
+ existing[k.strip] = v.to_s.strip unless k.strip.empty?
53
+ end
54
+ resp.set_cookies.each do |line|
55
+ pair = line.split(";", 2).first.to_s.strip
56
+ next unless pair.include?("=")
57
+
58
+ k, v = pair.split("=", 2)
59
+ existing[k.strip] = v.to_s.strip unless k.strip.empty?
60
+ end
61
+ existing.keys.sort.map { |k| "#{k}=#{existing[k]}" }.join("; ")
62
+ end
63
+
64
+ # 编码 session token
65
+ def encode_sess(cookie_hdr)
66
+ payload = JSON.generate("c" => cookie_hdr)
67
+ TOK_PREFIX + Base64.strict_encode64(payload)
68
+ end
69
+
70
+ # 解码 session token
71
+ def decode_sess(token)
72
+ raise "haribu: 无效的会话令牌" unless token.to_s.start_with?(TOK_PREFIX)
73
+
74
+ raw = token[TOK_PREFIX.length..]
75
+ data = JSON.parse(Base64.strict_decode64(raw))
76
+ cookie_hdr = (data["c"] || "").strip
77
+ raise "haribu: 会话令牌中缺少 cookie" if cookie_hdr.empty?
78
+
79
+ cookie_hdr
80
+ rescue ArgumentError, JSON::ParserError
81
+ raise "haribu: 无效的会话令牌"
82
+ end
83
+
84
+ # 从 HTML 中提取邮箱地址
85
+ def extract_email(html_str)
86
+ m = html_str.match(EMAIL_INPUT_RE) || html_str.match(EMAIL_INPUT_RE2)
87
+ raise "haribu: 未找到邮箱地址" unless m
88
+
89
+ addr = CGI.unescapeHTML(m[1]).strip
90
+ raise "haribu: 邮箱地址无效" unless addr.include?("@")
91
+
92
+ addr
93
+ end
94
+
95
+ # 创建临时邮箱
96
+ # @return [EmailInfo]
97
+ def generate_email
98
+ resp = Http.get(BASE, headers: HEADERS, timeout: 15)
99
+ resp.raise_for_status
100
+ raise "haribu: 首页响应为空" if resp.body.empty?
101
+
102
+ email_addr = extract_email(resp.body)
103
+ cookie_hdr = merge_cookies("", resp)
104
+ token = encode_sess(cookie_hdr)
105
+
106
+ EmailInfo.new(channel: CHANNEL, email: email_addr, token: token)
107
+ end
108
+
109
+ # 获取邮件详情页正文
110
+ def fetch_detail(detail_url, cookie_hdr)
111
+ hdrs = HEADERS.merge("Cookie" => cookie_hdr, "Referer" => BASE)
112
+ resp = Http.get(detail_url, headers: hdrs, timeout: 15)
113
+ return "" unless resp.ok?
114
+
115
+ m = resp.body.match(BODY_RE)
116
+ m ? m[1].strip : ""
117
+ rescue StandardError
118
+ ""
119
+ end
120
+
121
+ # 获取邮件列表
122
+ # @param token [String]
123
+ # @param email [String]
124
+ # @return [Array<Email>]
125
+ def get_emails(token, email)
126
+ e = (email || "").strip
127
+ raise "haribu: 邮箱地址为空" if e.empty?
128
+
129
+ cookie_hdr = decode_sess(token)
130
+
131
+ # 触发新邮件检查
132
+ begin
133
+ kontrol_hdrs = HEADERS.merge(
134
+ "Cookie" => cookie_hdr,
135
+ "Referer" => BASE,
136
+ "X-Requested-With" => "XMLHttpRequest"
137
+ )
138
+ Http.get("#{BASE}/en/api-kontrol/", headers: kontrol_hdrs, timeout: 15)
139
+ rescue StandardError
140
+ # 忽略
141
+ end
142
+
143
+ # GET 首页获取收件箱
144
+ inbox_hdrs = HEADERS.merge("Cookie" => cookie_hdr, "Referer" => BASE)
145
+ resp = Http.get(BASE, headers: inbox_hdrs, timeout: 15)
146
+ resp.raise_for_status
147
+
148
+ items = resp.body.scan(MAIL_ITEM_RE).map(&:first)
149
+ return [] if items.empty?
150
+
151
+ items.each_with_index.filter_map do |content, idx|
152
+ raw = { "id" => "haribu-#{idx}", "to" => e }
153
+
154
+ fm = content.match(FROM_RE)
155
+ raw["from"] = CGI.unescapeHTML(strip_tags(fm[1])).strip if fm
156
+
157
+ sm = content.match(SUBJECT_RE)
158
+ raw["subject"] = CGI.unescapeHTML(strip_tags(sm[1])).strip if sm
159
+
160
+ dm = content.match(DATE_RE)
161
+ raw["date"] = CGI.unescapeHTML(strip_tags(dm[1])).strip if dm
162
+
163
+ lm = content.match(MAIL_LINK_RE)
164
+ if lm
165
+ detail_url = lm[1]
166
+ detail_url = "#{BASE}/#{detail_url.sub(%r{\A/}, '')}" unless detail_url.start_with?("http")
167
+ html_body = fetch_detail(detail_url, cookie_hdr)
168
+ raw["html"] = html_body unless html_body.empty?
169
+ end
170
+
171
+ Normalize.normalize_email(raw, e)
172
+ end
173
+ end
174
+ end
175
+ end
176
+ end
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TempmailSdk
4
+ module Providers
5
+ # inboxes.com 渠道实现
6
+ module Inboxes
7
+ CHANNEL = "inboxes"
8
+ API_BASE = "https://inboxes.com/api/v2"
9
+ DEFAULT_DOMAIN = "blondmail.com"
10
+ HEADERS = {
11
+ "Accept" => "application/json",
12
+ "Origin" => "https://inboxes.com",
13
+ "Referer" => "https://inboxes.com/",
14
+ "User-Agent" => "Mozilla/5.0"
15
+ }.freeze
16
+
17
+ module_function
18
+
19
+ def random_local
20
+ chars = "abcdefghijklmnopqrstuvwxyz0123456789".chars
21
+ "sdk" + Array.new(16) { chars.sample }.join
22
+ end
23
+
24
+ # 获取可用域名列表
25
+ def get_domains
26
+ resp = Http.get("#{API_BASE}/domain", headers: HEADERS, timeout: 15)
27
+ resp.raise_for_status
28
+ data = resp.json
29
+ items = data.is_a?(Hash) ? (data["domains"] || []) : []
30
+ domains = items.select { |i| i.is_a?(Hash) && !i["qdn"].to_s.strip.empty? }
31
+ .map { |i| i["qdn"].to_s.strip }
32
+ raise "inboxes: no domains" if domains.empty?
33
+
34
+ domains
35
+ end
36
+
37
+ def pick_domain(domains, preferred)
38
+ wanted = preferred.to_s.strip.delete_prefix("@").downcase
39
+ unless wanted.empty?
40
+ domains.each { |d| return d if d.downcase == wanted }
41
+ end
42
+ return DEFAULT_DOMAIN if domains.include?(DEFAULT_DOMAIN)
43
+
44
+ domains.first
45
+ end
46
+
47
+ def flatten(raw, recipient)
48
+ {
49
+ "id" => (raw["uid"] || "").to_s,
50
+ "from" => (raw["sf"] || raw["f"] || "").to_s,
51
+ "to" => (raw["ib"] || recipient).to_s,
52
+ "subject" => (raw["s"] || "").to_s,
53
+ "text" => (raw["text"] || "").to_s,
54
+ "html" => (raw["html"] || "").to_s,
55
+ "preview_text" => (raw["ph"] || "").to_s,
56
+ "date" => (raw["cr"] || "").to_s,
57
+ "isRead" => !!raw["ru"],
58
+ "attachments" => raw["at"].is_a?(Array) ? raw["at"] : []
59
+ }
60
+ end
61
+
62
+ # @param domain [String, nil] 可选指定域名
63
+ # @return [EmailInfo]
64
+ def generate_email(domain = nil)
65
+ domains = get_domains
66
+ selected = pick_domain(domains, domain)
67
+ EmailInfo.new(channel: CHANNEL, email: "#{random_local}@#{selected}")
68
+ end
69
+
70
+ def fetch_detail(uid)
71
+ return nil if uid.to_s.strip.empty?
72
+
73
+ begin
74
+ resp = Http.get(
75
+ "#{API_BASE}/message/#{URI.encode_www_form_component(uid)}",
76
+ headers: HEADERS, timeout: 15
77
+ )
78
+ return nil unless resp.ok?
79
+
80
+ data = resp.json
81
+ data.is_a?(Hash) ? data : nil
82
+ rescue StandardError
83
+ nil
84
+ end
85
+ end
86
+
87
+ # @param email [String]
88
+ # @return [Array<Email>]
89
+ def get_emails(email)
90
+ address = email.to_s.strip
91
+ raise "inboxes: empty email" if address.empty?
92
+
93
+ resp = Http.get(
94
+ "#{API_BASE}/inbox/#{URI.encode_www_form_component(address)}",
95
+ headers: HEADERS, timeout: 15
96
+ )
97
+ resp.raise_for_status
98
+ data = resp.json
99
+ rows = data.is_a?(Hash) ? (data["msgs"] || []) : []
100
+ return [] unless rows.is_a?(Array)
101
+
102
+ rows.select { |r| r.is_a?(Hash) }.map do |row|
103
+ uid = (row["uid"] || "").to_s.strip
104
+ detail = uid.empty? ? nil : fetch_detail(uid)
105
+ Normalize.normalize_email(flatten(detail || row, address), address)
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TempmailSdk
4
+ module Providers
5
+ # InboxKitten 渠道 — https://inboxkitten.com
6
+ module Inboxkitten
7
+ CHANNEL = "inboxkitten"
8
+ API_BASE = "https://inboxkitten.com/api/v1/mail"
9
+ DOMAIN = "inboxkitten.com"
10
+ HEADERS_JSON = { "Accept" => "application/json" }.freeze
11
+ HEADERS_HTML = { "Accept" => "text/html,*/*" }.freeze
12
+
13
+ module_function
14
+
15
+ def random_local
16
+ chars = "abcdefghijklmnopqrstuvwxyz0123456789".chars
17
+ "sdk" + Array.new(16) { chars.sample }.join
18
+ end
19
+
20
+ # 无需请求服务端,随机本地部分即可
21
+ # @return [EmailInfo]
22
+ def generate_email
23
+ EmailInfo.new(channel: CHANNEL, email: "#{random_local}@#{DOMAIN}")
24
+ end
25
+
26
+ def fetch_meta(region, key)
27
+ resp = Http.get(
28
+ "#{API_BASE}/getKey?region=#{URI.encode_www_form_component(region)}&key=#{URI.encode_www_form_component(key)}",
29
+ headers: HEADERS_JSON, timeout: 15
30
+ )
31
+ resp.raise_for_status
32
+ data = resp.json
33
+ data.is_a?(Hash) ? data : {}
34
+ end
35
+
36
+ def fetch_html(region, key)
37
+ resp = Http.get(
38
+ "#{API_BASE}/getHtml?region=#{URI.encode_www_form_component(region)}&key=#{URI.encode_www_form_component(key)}",
39
+ headers: HEADERS_HTML, timeout: 15
40
+ )
41
+ resp.raise_for_status
42
+ resp.body
43
+ end
44
+
45
+ def detail_raw(row, recipient)
46
+ storage = row["storage"].is_a?(Hash) ? row["storage"] : {}
47
+ message = row["message"].is_a?(Hash) ? row["message"] : {}
48
+ headers = message["headers"].is_a?(Hash) ? message["headers"] : {}
49
+ envelope = row["envelope"].is_a?(Hash) ? row["envelope"] : {}
50
+ region = storage["region"].to_s.strip
51
+ key = storage["key"].to_s.strip
52
+ raw = {
53
+ "id" => key, "messageId" => key,
54
+ "from" => headers["from"] || envelope["sender"] || "",
55
+ "to" => row["recipient"] || envelope["targets"] || recipient,
56
+ "subject" => headers["subject"] || "", "timestamp" => row["timestamp"]
57
+ }
58
+ return raw if region.empty? || key.empty?
59
+
60
+ begin
61
+ meta = fetch_meta(region, key)
62
+ html = fetch_html(region, key)
63
+ raw["from"] = meta["name"] || raw["from"]
64
+ raw["to"] = meta["recipients"] || raw["to"]
65
+ raw["subject"] = meta["subject"] || raw["subject"]
66
+ raw["text"] = HtmlUtils.html_to_text(html)
67
+ raw["html"] = html
68
+ rescue StandardError
69
+ # 忽略详情失败
70
+ end
71
+ raw
72
+ end
73
+
74
+ # @param email [String]
75
+ # @return [Array<Email>]
76
+ def get_emails(email)
77
+ local = email.to_s.strip.split("@").first.to_s
78
+ raise "inboxkitten: empty email" if local.empty?
79
+
80
+ resp = Http.get("#{API_BASE}/list?recipient=#{URI.encode_www_form_component(local)}",
81
+ headers: HEADERS_JSON, timeout: 15)
82
+ resp.raise_for_status
83
+ rows = resp.json
84
+ return [] unless rows.is_a?(Array)
85
+
86
+ rows.select { |r| r.is_a?(Hash) }.map { |item| Normalize.normalize_email(detail_raw(item, email), email) }
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,157 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "cgi"
4
+
5
+ module TempmailSdk
6
+ module Providers
7
+ # linshiyou.com 临时邮 — NEXUS_TOKEN + tmail-emails Cookie,邮件接口返回 HTML 分片
8
+ module Linshiyou
9
+ CHANNEL = "linshiyou"
10
+ ORIGIN = "https://linshiyou.com"
11
+
12
+ DEFAULT_HEADERS = {
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
+ "accept-language" => "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
16
+ "cache-control" => "no-cache",
17
+ "dnt" => "1",
18
+ "pragma" => "no-cache",
19
+ "priority" => "u=1, i",
20
+ "referer" => "#{ORIGIN}/",
21
+ "sec-ch-ua" => '"Chromium";v="146", "Not-A.Brand";v="24", "Microsoft Edge";v="146"',
22
+ "sec-ch-ua-mobile" => "?0",
23
+ "sec-ch-ua-platform" => '"Windows"',
24
+ "sec-fetch-dest" => "empty",
25
+ "sec-fetch-mode" => "cors",
26
+ "sec-fetch-site" => "same-origin"
27
+ }.freeze
28
+
29
+ RE_LIST_ID = /id="tmail-email-list-([a-f0-9]+)"/i
30
+ RE_DIV = /<div class="([^"]+)"[^>]*>([\s\S]*?)<\/div>/i
31
+ RE_SRCDOC = /\ssrcdoc="([^"]*)"/i
32
+ RE_DOWNLOAD = /href="(\/api\/download\?id=[^"]+)"/i
33
+ RE_TAGS = /<[^>]+>/
34
+
35
+ module_function
36
+
37
+ def strip_tags(s)
38
+ t = s.gsub(RE_TAGS, " ")
39
+ CGI.unescapeHTML(t).split.join(" ")
40
+ end
41
+
42
+ def pick_div(fragment, class_name)
43
+ fragment.scan(RE_DIV).each do |cls, inner|
44
+ return strip_tags(inner).strip if cls == class_name
45
+ end
46
+ ""
47
+ end
48
+
49
+ def parse_date(s)
50
+ s = s.to_s.strip
51
+ return "" if s.empty?
52
+
53
+ ["%Y-%m-%d %H:%M:%S", "%Y-%m-%dT%H:%M:%S"].each do |fmt|
54
+ begin
55
+ dt = Time.strptime(s, fmt)
56
+ return dt.utc.strftime("%Y-%m-%dT%H:%M:%SZ")
57
+ rescue ArgumentError
58
+ next
59
+ end
60
+ end
61
+ ""
62
+ end
63
+
64
+ def extract_srcdoc(body_part)
65
+ m = body_part.match(RE_SRCDOC)
66
+ return "" unless m
67
+
68
+ CGI.unescapeHTML(m[1])
69
+ end
70
+
71
+ # 解析邮件分段
72
+ def parse_segments(raw, recipient)
73
+ raw = raw.to_s.strip
74
+ return [] if raw.empty?
75
+
76
+ out = []
77
+ raw.split("<-----TMAILNEXTMAIL----->").each do |seg|
78
+ seg = seg.strip
79
+ next if seg.empty?
80
+
81
+ parts = seg.split("<-----TMAILCHOPPER----->", 2)
82
+ list_part = parts[0]
83
+ body_part = parts.length > 1 ? parts[1] : ""
84
+
85
+ mid = list_part.match(RE_LIST_ID)
86
+ next unless mid
87
+
88
+ mid_str = mid[1]
89
+
90
+ from_list = pick_div(list_part, "name")
91
+ subject_list = pick_div(list_part, "subject")
92
+ preview_list = pick_div(list_part, "body")
93
+
94
+ from_body = pick_div(body_part, "tmail-email-sender")
95
+ time_body = pick_div(body_part, "tmail-email-time")
96
+ title_body = pick_div(body_part, "tmail-email-title")
97
+ html_body = extract_srcdoc(body_part)
98
+
99
+ from_addr = from_body.empty? ? from_list : from_body
100
+ subject = title_body.empty? ? subject_list : title_body
101
+ text = preview_list.empty? ? strip_tags(html_body) : preview_list
102
+
103
+ attachments = []
104
+ dm = body_part.match(RE_DOWNLOAD)
105
+ if dm
106
+ attachments << EmailAttachment.new(filename: "", url: "#{ORIGIN}#{dm[1]}")
107
+ end
108
+
109
+ out << Email.new(
110
+ id: mid_str,
111
+ from_addr: from_addr,
112
+ to: recipient,
113
+ subject: subject,
114
+ text: text,
115
+ html: html_body,
116
+ date: parse_date(time_body),
117
+ is_read: false,
118
+ attachments: attachments
119
+ )
120
+ end
121
+ out
122
+ end
123
+
124
+ # 创建临时邮箱
125
+ # @return [EmailInfo]
126
+ def generate_email
127
+ resp = Http.get("#{ORIGIN}/api/user?user", headers: DEFAULT_HEADERS)
128
+ resp.raise_for_status
129
+
130
+ nexus = resp.cookie_value("NEXUS_TOKEN")
131
+ raise "linshiyou: NEXUS_TOKEN not in Set-Cookie" if nexus.nil? || nexus.empty?
132
+
133
+ email = resp.body.strip
134
+ raise "linshiyou: invalid email in response body" if email.empty? || !email.include?("@")
135
+
136
+ token = "NEXUS_TOKEN=#{nexus}; tmail-emails=#{email}"
137
+ EmailInfo.new(channel: CHANNEL, email: email, token: token)
138
+ end
139
+
140
+ # 获取邮件列表
141
+ # @param token [String]
142
+ # @param email [String]
143
+ # @return [Array<Email>]
144
+ def get_emails(token, email = "")
145
+ resp = Http.get(
146
+ "#{ORIGIN}/api/mail?unseen=1",
147
+ headers: DEFAULT_HEADERS.merge(
148
+ "Cookie" => token,
149
+ "x-requested-with" => "XMLHttpRequest"
150
+ )
151
+ )
152
+ resp.raise_for_status
153
+ parse_segments(resp.body, email)
154
+ end
155
+ end
156
+ end
157
+ end