Module: TempmailSdk::Normalize

Defined in:
lib/tempmail_sdk/normalize.rb

Overview

邮件数据标准化模块 将各提供商返回的原始邮件数据(Hash,键为字符串)标准化为统一的 Email 格式

Constant Summary collapse

ID_KEYS =

各字段的候选键顺序(frozen 常量,避免每次调用重复分配数组)

%w[id eid _id mailboxId messageId mail_id].freeze
FROM_KEYS =
%w[from_addr from_address fromAddress mail_sender sender
address_from from_email from messageFrom].freeze
TO_KEYS =
%w[to to_address toAddress name_to email_address address].freeze
SUBJECT_KEYS =
%w[subject e_subject mail_title].freeze
TEXT_KEYS =
%w[text text_body preview_text mail_body_text body content
body_text text_content description].freeze
HTML_KEYS =
%w[html html_body html_content body_html mail_body_html].freeze
DATE_STR_KEYS =
%w[received_at receivedAt created_at createdAt date].freeze
DATE_NUM_KEYS =
%w[timestamp e_date].freeze
IS_READ_BOOL_KEYS =
%w[seen read isRead].freeze
IS_READ_MIXED_KEYS =
%w[is_read is_seen].freeze
TRAILING_Z_RE =

日期解析用预编译正则(避免方法内每次新建 Regexp)

/Z\z/.freeze

Class Method Summary collapse

Class Method Details

.get_str(raw, *keys) ⇒ Object

从 Hash 中按优先级顺序提取字符串值



80
81
82
# File 'lib/tempmail_sdk/normalize.rb', line 80

def get_str(raw, *keys)
  get_str_keys(raw, keys)
end

.get_str_keys(raw, keys) ⇒ Object

从 Hash 中按给定候选键数组(可复用 frozen 常量)提取字符串值



85
86
87
88
89
90
91
# File 'lib/tempmail_sdk/normalize.rb', line 85

def get_str_keys(raw, keys)
  keys.each do |key|
    val = raw[key]
    return val.to_s unless val.nil?
  end
  ""
end

.html_content?(content) ⇒ Boolean

检测内容是否为 HTML(只取前 200 字符)

Returns:

  • (Boolean)


67
68
69
70
71
72
73
74
75
76
77
# File 'lib/tempmail_sdk/normalize.rb', line 67

def html_content?(content)
  prefix = content[0, 200].to_s.strip.downcase
  return true if prefix.start_with?("<!doctype html", "<html", "<body")

  trimmed = content.strip.downcase
  return true if trimmed.include?("<div") && trimmed.include?("</div>")
  return true if trimmed.include?("<table") && trimmed.include?("</table>")
  return true if trimmed.include?("<p") && trimmed.include?("</p>") && trimmed.include?("<")

  false
end

.normalize_attachments(attachments) ⇒ Object

提取并标准化附件列表



177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
# File 'lib/tempmail_sdk/normalize.rb', line 177

def normalize_attachments(attachments)
  return [] unless attachments.is_a?(Array)

  attachments.filter_map do |a|
    a = stringify_keys(a)
    next unless a.is_a?(Hash)

    EmailAttachment.new(
      filename: a["filename"] || a["name"] || "",
      size: a["size"] || a["filesize"],
      content_type: a["contentType"] || a["content_type"] || a["mimeType"] || a["mime_type"],
      url: a["url"] || a["download_url"] || a["downloadUrl"]
    )
  end
end

.normalize_date(raw) ⇒ Object

提取并统一日期格式为 ISO 8601



119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/tempmail_sdk/normalize.rb', line 119

def normalize_date(raw)
  DATE_STR_KEYS.each do |key|
    val = raw[key]
    next if val.nil?

    return parse_date_value(val)
  end

  DATE_NUM_KEYS.each do |key|
    val = raw[key]
    next unless val.is_a?(Numeric) && val.positive?

    secs = key == "timestamp" && val < 1e12 ? val : val / 1000.0
    return Time.at(secs).utc.iso8601
  end

  ""
end

.normalize_email(raw, recipient_email = "") ⇒ Email

将各提供商返回的原始邮件数据标准化为统一的 Email 格式 不同渠道字段名各异,此函数通过多字段候选策略统一映射

Parameters:

  • raw (Hash)

    原始邮件数据(字符串键)

  • recipient_email (String) (defaults to: "")

    收件人邮箱(无匹配字段时回退)

Returns:



33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/tempmail_sdk/normalize.rb', line 33

def normalize_email(raw, recipient_email = "")
  raw = stringify_keys(raw)
  text = normalize_text(raw)
  html = normalize_html(raw)

  # 修正 text/html 错配
  if !text.empty? && html.empty? && html_content?(text)
    html = text
    text = ""
  end
  text = HtmlUtils.html_to_text(html) if text.empty? && !html.empty?
  html = "<html><body><pre>#{HtmlUtils.escape(text)}</pre></body></html>" if !text.empty? && html.empty?

  Email.new(
    id: normalize_id(raw),
    from_addr: normalize_from(raw),
    to: normalize_to(raw, recipient_email),
    subject: normalize_subject(raw),
    text: text,
    html: html,
    date: normalize_date(raw),
    is_read: normalize_is_read(raw),
    attachments: normalize_attachments(raw["attachments"])
  )
end

.normalize_from(raw) ⇒ Object



97
98
99
# File 'lib/tempmail_sdk/normalize.rb', line 97

def normalize_from(raw)
  get_str_keys(raw, FROM_KEYS)
end

.normalize_html(raw) ⇒ Object



114
115
116
# File 'lib/tempmail_sdk/normalize.rb', line 114

def normalize_html(raw)
  get_str_keys(raw, HTML_KEYS)
end

.normalize_id(raw) ⇒ Object



93
94
95
# File 'lib/tempmail_sdk/normalize.rb', line 93

def normalize_id(raw)
  get_str_keys(raw, ID_KEYS)
end

.normalize_is_read(raw) ⇒ Object

提取已读状态,支持 bool / int(0|1) / str('0'|'1')



158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
# File 'lib/tempmail_sdk/normalize.rb', line 158

def normalize_is_read(raw)
  IS_READ_BOOL_KEYS.each do |key|
    val = raw[key]
    return val if val == true || val == false
  end

  IS_READ_MIXED_KEYS.each do |key|
    val = raw[key]
    next if val.nil?

    return val if [true, false].include?(val)
    return val.to_i != 0 if val.is_a?(Numeric)
    return val == "1" if val.is_a?(String)
  end

  false
end

.normalize_subject(raw) ⇒ Object



106
107
108
# File 'lib/tempmail_sdk/normalize.rb', line 106

def normalize_subject(raw)
  get_str_keys(raw, SUBJECT_KEYS)
end

.normalize_text(raw) ⇒ Object



110
111
112
# File 'lib/tempmail_sdk/normalize.rb', line 110

def normalize_text(raw)
  get_str_keys(raw, TEXT_KEYS)
end

.normalize_to(raw, recipient_email) ⇒ Object



101
102
103
104
# File 'lib/tempmail_sdk/normalize.rb', line 101

def normalize_to(raw, recipient_email)
  result = get_str_keys(raw, TO_KEYS)
  result.empty? ? recipient_email : result
end

.parse_date_value(val) ⇒ Object

解析单个日期值(字符串/秒/毫秒时间戳)



139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# File 'lib/tempmail_sdk/normalize.rb', line 139

def parse_date_value(val)
  if val.is_a?(String) && !val.empty?
    begin
      return Time.iso8601(val.sub(TRAILING_Z_RE, "+00:00")).iso8601
    rescue ArgumentError
      begin
        return Time.strptime("#{val} UTC", "%Y-%m-%d %H:%M:%S %Z").utc.iso8601
      rescue ArgumentError
        return val
      end
    end
  elsif val.is_a?(Numeric) && val.positive?
    secs = val > 1e12 ? val / 1000.0 : val
    return Time.at(secs).utc.iso8601
  end
  ""
end

.stringify_keys(raw) ⇒ Object

将 Hash 的符号键统一转为字符串键(浅层)



60
61
62
63
64
# File 'lib/tempmail_sdk/normalize.rb', line 60

def stringify_keys(raw)
  return {} unless raw.is_a?(Hash)

  raw.each_with_object({}) { |(k, v), h| h[k.to_s] = v }
end