Module: TempmailSdk::Providers::Anonbox
- Defined in:
- lib/tempmail_sdk/providers/anonbox.rb
Overview
anonbox.net (CCC) — GET /en/ 解析 HTML,HTTP mbox 取信
Constant Summary collapse
- CHANNEL =
"anonbox"- PAGE_URL =
"https://anonbox.net/en/"- BASE =
"https://anonbox.net"- MAIL_LINK_RE =
%r{<a href="https://anonbox\.net/([a-z0-9-]+)/([A-Za-z0-9._~-]+)">https://anonbox\.net/[^"]+</a>}i- DD_RE =
/<dd([^>]*)>([\s\S]*?)<\/dd>/mi- DISPLAY_NONE_RE =
/display\s*:\s*none/i- P_RE =
/<p>([\s\S]*?)<\/p>/mi- SPAN_RE =
/<span\b[^>]*>[\s\S]*?<\/span>/mi- TAG_RE =
/<[^>]+>/- EXPIRES_RE =
/Your mail address is valid until:<\/dt>\s*<dd><p>([^<]+)<\/p>/mi- MBOX_SPLIT_RE =
/\r?\n(?=From )/- HEADERS =
{ "User-Agent" => "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", "Accept" => "text/html,application/xhtml+xml" }.freeze
Class Method Summary collapse
-
.decode_qp_if_needed(body, header_block) ⇒ Object
解码 quoted-printable 编码.
-
.generate_email ⇒ EmailInfo
创建 anonbox 临时邮箱.
-
.get_emails(token, email) ⇒ Array<Email>
获取邮件列表.
-
.mbox_block_to_raw(block, recipient) ⇒ Object
解析 mbox 格式单条邮件.
-
.parse_en_page(html_str) ⇒ Object
从首页 HTML 中解析邮箱地址、token、过期时间.
-
.simple_hash(s) ⇒ Object
简单哈希,用于生成邮件 ID.
-
.strip_tags(html_str) ⇒ Object
去除 HTML 标签并还原常见实体.
Class Method Details
.decode_qp_if_needed(body, header_block) ⇒ Object
解码 quoted-printable 编码
102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 |
# File 'lib/tempmail_sdk/providers/anonbox.rb', line 102 def decode_qp_if_needed(body, header_block) te = header_block.match(/(?i)^content-transfer-encoding:\s*([^\s]+)/m) enc = te ? te[1].downcase.strip : "" return body.rstrip unless enc == "quoted-printable" soft = body.gsub(/=\r?\n/, "") result = [] bytes = soft.bytes i = 0 while i < bytes.length if bytes[i] == 61 && i + 2 < bytes.length # '=' hex = [bytes[i + 1], bytes[i + 2]].pack("C*") begin result << hex.to_i(16) i += 3 next rescue ArgumentError # 非法十六进制,作为普通字符处理 end end result << bytes[i] i += 1 end result.pack("C*").force_encoding("UTF-8").rstrip end |
.generate_email ⇒ EmailInfo
创建 anonbox 临时邮箱
220 221 222 223 224 225 |
# File 'lib/tempmail_sdk/providers/anonbox.rb', line 220 def generate_email r = Http.get(PAGE_URL, headers: HEADERS, timeout: 15) r.raise_for_status email, token, exp = parse_en_page(r.body) EmailInfo.new(channel: CHANNEL, email: email, token: token, created_at: exp) end |
.get_emails(token, email) ⇒ Array<Email>
获取邮件列表
231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 |
# File 'lib/tempmail_sdk/providers/anonbox.rb', line 231 def get_emails(token, email) raise "token is required for anonbox channel" if token.nil? || token.to_s.empty? path = token.end_with?("/") ? token : "#{token}/" url = "#{BASE}/#{path}" r = Http.get(url, headers: HEADERS.merge("Accept" => "text/plain,*/*"), timeout: 15) return [] if r.status_code == 404 r.raise_for_status raw = r.body.strip return [] if raw.empty? blocks = raw.split(MBOX_SPLIT_RE).map(&:strip).reject(&:empty?) blocks.map { |b| Normalize.normalize_email(mbox_block_to_raw(b, email), email) } end |
.mbox_block_to_raw(block, recipient) ⇒ Object
解析 mbox 格式单条邮件
129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 |
# File 'lib/tempmail_sdk/providers/anonbox.rb', line 129 def mbox_block_to_raw(block, recipient) normalized = block.gsub("\r\n", "\n") lines = normalized.split("\n") i = 0 i = 1 if lines.first&.start_with?("From ") headers = {} cur_key = "" while i < lines.length line = lines[i] if line == "" i += 1 break end if (line[0] == " " || line[0] == "\t") && !cur_key.empty? headers[cur_key] = "#{headers[cur_key]} #{line.strip}" else idx = line.index(":") if idx && idx > 0 cur_key = line[0...idx].strip.downcase headers[cur_key] = line[(idx + 1)..].strip end end i += 1 end body = lines[i..].join("\n") ct = (headers["content-type"] || "").downcase text = "" html = "" if ct.include?("multipart/") bm = headers["content-type"]&.match(/(?i)boundary="?([^";\s]+)"?/) if bm boundary = Regexp.escape(bm[1]) parts = body.split(/\r?\n--#{boundary}(?:--)?\r?\n/) parts.each do |part| part = part.strip next if part.empty? || part == "--" sep = part.index("\n\n") next unless sep ph = part[0...sep] pb = part[(sep + 2)..] pct_m = ph.match(/(?i)^content-type:\s*([^\s;]+)/m) pct = pct_m ? pct_m[1].downcase : "" if pct == "text/plain" text = decode_qp_if_needed(pb, ph) elsif pct == "text/html" html = decode_qp_if_needed(pb, ph) end end end end if text.empty? && html.empty? if ct.include?("text/html") html = decode_qp_if_needed(body, headers["content-type"] || "") else text = decode_qp_if_needed(body, headers["content-type"] || "") end end date_str = headers["date"] || "" unless date_str.empty? begin date_str = Time.parse(date_str.strip).iso8601 rescue ArgumentError, TypeError # 保持原值 end end date_str = Time.now.utc.iso8601 if date_str.empty? msg_id = headers["message-id"] || simple_hash(block[0, 512]) { "id" => msg_id, "from" => headers["from"] || "", "to" => headers["to"] || recipient, "subject" => headers["subject"] || "", "body_text" => text, "body_html" => html, "date" => date_str, "isRead" => false, "attachments" => [] } end |
.parse_en_page(html_str) ⇒ Object
从首页 HTML 中解析邮箱地址、token、过期时间
54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 |
# File 'lib/tempmail_sdk/providers/anonbox.rb', line 54 def parse_en_page(html_str) m = html_str.match(MAIL_LINK_RE) raise "anonbox: mailbox link not found" unless m inbox = m[1] secret = m[2] token = "#{inbox}/#{secret}" # 从 <dd> 中提取邮箱地址 address_html = nil html_str.scan(DD_RE).each do |attrs, inner| next if attrs.match?(DISPLAY_NONE_RE) pm = inner.match(P_RE) next unless pm p_inner = pm[1].gsub(SPAN_RE, "") display = (p_inner) next unless display.include?("@") at = display.rindex("@") local = display[0...at].strip domain = display[(at + 1)..].strip.downcase next if domain == "googlemail.com" expected_domain = "#{inbox}.anonbox.net".downcase next if domain != expected_domain next if local.empty? address_html = display break end raise "anonbox: address paragraph not found" unless address_html at = address_html.index("@") raise "anonbox: bad address" unless at local = address_html[0...at].strip raise "anonbox: empty local part" if local.empty? email = "#{local}@#{inbox}.anonbox.net" em = html_str.match(EXPIRES_RE) exp = em ? em[1].strip : nil [email, token, exp] end |
.simple_hash(s) ⇒ Object
简单哈希,用于生成邮件 ID
38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
# File 'lib/tempmail_sdk/providers/anonbox.rb', line 38 def simple_hash(s) h = 0 s.each_char { |c| h = (h * 31 + c.ord) & 0xFFFFFFFF } return "0" if h == 0 digits = "0123456789abcdefghijklmnopqrstuvwxyz" out = [] n = h while n > 0 out.unshift(digits[n % 36]) n /= 36 end out.join end |
.strip_tags(html_str) ⇒ Object
去除 HTML 标签并还原常见实体
28 29 30 31 32 33 34 35 |
# File 'lib/tempmail_sdk/providers/anonbox.rb', line 28 def (html_str) s = html_str.gsub(TAG_RE, "") s.gsub(" ", " ") .gsub("&", "&") .gsub("<", "<") .gsub(">", ">") .strip end |