145 lines
3.4 KiB
Go
145 lines
3.4 KiB
Go
package mailx
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
// FromBytes 从 JSON 字节构建消息(便捷入口,用于接入配置/存储)。
|
|
func FromBytes(data []byte) (*Message, error) {
|
|
var m Message
|
|
if err := json.Unmarshal(data, &m); err != nil {
|
|
return nil, fmt.Errorf("mailx: unmarshal message: %w", err)
|
|
}
|
|
return &m, nil
|
|
}
|
|
|
|
// FromMap 从 map 构建消息,常用字段自动映射:
|
|
// - from / to / cc / bcc / subject / text / html / body / replyto / reply_to
|
|
// - to/cc/bcc 可为 []string、[]any 或 "a@x.com,b@y.com" 分隔串
|
|
// - attachments: []map{name,path,data} 或 []string(path)
|
|
func FromMap(m map[string]any) (*Message, error) {
|
|
if m == nil {
|
|
return nil, fmt.Errorf("%w: map is nil", ErrInvalidConfig)
|
|
}
|
|
msg := &Message{}
|
|
var err error
|
|
|
|
str := func(keys ...string) string {
|
|
for _, k := range keys {
|
|
if v, ok := m[k].(string); ok {
|
|
return v
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
msg.From = str("from")
|
|
msg.Subject = str("subject")
|
|
msg.TextBody = firstNonEmptyStr(str("text"), str("textbody"), str("text_body"))
|
|
msg.Body = firstNonEmptyStr(str("html"), str("body"))
|
|
msg.ReplyTo = firstNonEmptyStr(str("replyto"), str("reply_to"))
|
|
|
|
if msg.To, err = toList(m["to"]); err != nil {
|
|
return nil, err
|
|
}
|
|
if msg.Cc, err = toList(m["cc"]); err != nil {
|
|
return nil, err
|
|
}
|
|
if msg.Bcc, err = toList(m["bcc"]); err != nil {
|
|
return nil, err
|
|
}
|
|
if atts, ok := m["attachments"]; ok {
|
|
msg.Attachments, err = toAttachments(atts)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
return msg, nil
|
|
}
|
|
|
|
// toList 将任意输入转为地址列表([]string、[]any 或逗号/分号分隔串)
|
|
func toList(v any) ([]string, error) {
|
|
switch t := v.(type) {
|
|
case nil:
|
|
return nil, nil
|
|
case []string:
|
|
return t, nil
|
|
case string:
|
|
var out []string
|
|
for _, p := range strings.FieldsFunc(t, func(r rune) bool { return r == ',' || r == ';' }) {
|
|
if p = strings.TrimSpace(p); p != "" {
|
|
out = append(out, p)
|
|
}
|
|
}
|
|
return out, nil
|
|
case []any:
|
|
out := make([]string, 0, len(t))
|
|
for _, item := range t {
|
|
if s, ok := item.(string); ok {
|
|
out = append(out, s)
|
|
}
|
|
}
|
|
return out, nil
|
|
default:
|
|
return nil, fmt.Errorf("mailx: invalid address list type %T", v)
|
|
}
|
|
}
|
|
|
|
// toAttachments 将 attachments 输入转为附件列表
|
|
func toAttachments(v any) ([]Attachment, error) {
|
|
switch t := v.(type) {
|
|
case []string:
|
|
out := make([]Attachment, 0, len(t))
|
|
for _, p := range t {
|
|
out = append(out, Attachment{Name: fileBaseName(p), Path: p})
|
|
}
|
|
return out, nil
|
|
case []map[string]any:
|
|
out := make([]Attachment, 0, len(t))
|
|
for _, mm := range t {
|
|
var att Attachment
|
|
if name, ok := mm["name"].(string); ok {
|
|
att.Name = name
|
|
}
|
|
if p, ok := mm["path"].(string); ok {
|
|
att.Path = p
|
|
if att.Name == "" {
|
|
att.Name = fileBaseName(p)
|
|
}
|
|
}
|
|
if d, ok := mm["data"]; ok {
|
|
if bs, ok := d.([]byte); ok {
|
|
att.Data = bs
|
|
}
|
|
}
|
|
out = append(out, att)
|
|
}
|
|
return out, nil
|
|
case []any:
|
|
return toAttachments(mapsFromAny(t))
|
|
default:
|
|
return nil, fmt.Errorf("mailx: invalid attachments type %T", v)
|
|
}
|
|
}
|
|
|
|
// mapsFromAny 将 []any 转为 []map[string]any(忽略非 map 元素)
|
|
func mapsFromAny(arr []any) []map[string]any {
|
|
out := make([]map[string]any, 0, len(arr))
|
|
for _, item := range arr {
|
|
if mm, ok := item.(map[string]any); ok {
|
|
out = append(out, mm)
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
func firstNonEmptyStr(vals ...string) string {
|
|
for _, v := range vals {
|
|
if v != "" {
|
|
return v
|
|
}
|
|
}
|
|
return ""
|
|
}
|