support imap
This commit is contained in:
parent
182b2fd96f
commit
cdd6e2968d
42
go.mod
42
go.mod
@ -1,34 +1,38 @@
|
|||||||
module apigo.cc/gojs/mail
|
module apigo.cc/gojs/mail
|
||||||
|
|
||||||
go 1.18
|
go 1.24
|
||||||
|
|
||||||
require (
|
require (
|
||||||
apigo.cc/gojs v0.0.12
|
apigo.cc/gojs v0.0.25
|
||||||
apigo.cc/gojs/console v0.0.2
|
apigo.cc/gojs/console v0.0.2
|
||||||
apigo.cc/gojs/redis v0.0.2
|
github.com/emersion/go-imap/v2 v2.0.0-beta.5
|
||||||
github.com/ssgo/u v1.7.13
|
github.com/emersion/go-message v0.18.1
|
||||||
|
github.com/jaytaylor/html2text v0.0.0-20230321000545-74c2419ad056
|
||||||
|
github.com/ssgo/config v1.7.9
|
||||||
|
github.com/ssgo/u v1.7.21
|
||||||
|
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/dlclark/regexp2 v1.11.4 // indirect
|
github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a // indirect
|
||||||
github.com/emersion/go-imap v1.2.1 // indirect
|
github.com/dlclark/regexp2 v1.11.5 // indirect
|
||||||
github.com/fsnotify/fsnotify v1.8.0 // indirect
|
github.com/emersion/go-sasl v0.0.0-20231106173351-e73c9f7bad43 // indirect
|
||||||
|
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||||
github.com/go-sourcemap/sourcemap v2.1.4+incompatible // indirect
|
github.com/go-sourcemap/sourcemap v2.1.4+incompatible // indirect
|
||||||
github.com/gomodule/redigo v1.9.2 // indirect
|
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f // indirect
|
||||||
github.com/google/pprof v0.0.0-20230207041349-798e818bf904 // indirect
|
github.com/google/pprof v0.0.0-20250630185457-6e76a2b096b5 // indirect
|
||||||
github.com/jaytaylor/html2text v0.0.0-20230321000545-74c2419ad056 // indirect
|
github.com/jhillyerd/enmime v1.3.0 // indirect
|
||||||
github.com/mattn/go-runewidth v0.0.9 // indirect
|
github.com/mattn/go-runewidth v0.0.15 // indirect
|
||||||
github.com/olekukonko/tablewriter v0.0.5 // indirect
|
github.com/olekukonko/tablewriter v0.0.5 // indirect
|
||||||
github.com/ssgo/config v1.7.9 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
github.com/ssgo/log v1.7.7 // indirect
|
github.com/rivo/uniseg v0.4.4 // indirect
|
||||||
github.com/ssgo/redis v1.7.7 // indirect
|
github.com/ssgo/log v1.7.9 // indirect
|
||||||
github.com/ssgo/standard v1.7.7 // indirect
|
github.com/ssgo/standard v1.7.7 // indirect
|
||||||
github.com/ssgo/tool v0.4.28 // indirect
|
github.com/ssgo/tool v0.4.29 // indirect
|
||||||
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
|
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
|
||||||
golang.org/x/net v0.32.0 // indirect
|
golang.org/x/net v0.42.0 // indirect
|
||||||
golang.org/x/sys v0.28.0 // indirect
|
golang.org/x/sys v0.34.0 // indirect
|
||||||
golang.org/x/text v0.21.0 // indirect
|
golang.org/x/text v0.27.0 // indirect
|
||||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df // indirect
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
370
imap.go
Normal file
370
imap.go
Normal file
@ -0,0 +1,370 @@
|
|||||||
|
package mail
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"apigo.cc/gojs"
|
||||||
|
"apigo.cc/gojs/goja"
|
||||||
|
"github.com/emersion/go-imap/v2"
|
||||||
|
"github.com/emersion/go-imap/v2/imapclient"
|
||||||
|
"github.com/jhillyerd/enmime"
|
||||||
|
"github.com/ssgo/u"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Mail struct {
|
||||||
|
Uid uint32
|
||||||
|
From []string
|
||||||
|
To []string
|
||||||
|
Cc []string
|
||||||
|
Bcc []string
|
||||||
|
Subject string
|
||||||
|
Date string
|
||||||
|
Text string
|
||||||
|
Html string
|
||||||
|
Attachments []Attachment
|
||||||
|
Embeds []Attachment
|
||||||
|
Size int64
|
||||||
|
Flags []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// 接收选项
|
||||||
|
type RecvOption struct {
|
||||||
|
Tag uint32
|
||||||
|
Mailbox string
|
||||||
|
Unseen bool
|
||||||
|
Read bool
|
||||||
|
Answered bool
|
||||||
|
Flagged bool
|
||||||
|
Deleted bool
|
||||||
|
Since string
|
||||||
|
Before string
|
||||||
|
Limit uint32
|
||||||
|
MarkAsRead bool
|
||||||
|
|
||||||
|
Subject []string
|
||||||
|
Search []string
|
||||||
|
Body []string
|
||||||
|
From []string
|
||||||
|
To []string
|
||||||
|
Cc []string
|
||||||
|
Bcc []string
|
||||||
|
Not []string
|
||||||
|
}
|
||||||
|
|
||||||
|
type MailboxInfo struct {
|
||||||
|
Name string
|
||||||
|
Delimiter string
|
||||||
|
Attrs []string
|
||||||
|
}
|
||||||
|
|
||||||
|
type RecvResult struct {
|
||||||
|
List []Mail
|
||||||
|
Tag uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
type IMAPClient struct {
|
||||||
|
client *imapclient.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func (obj *IMAPClient) Close(vm *goja.Runtime) {
|
||||||
|
obj.client.Logout().Wait()
|
||||||
|
obj.client.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (obj *Mailbox) OpenIMAP(vm *goja.Runtime) (*IMAPClient, error) {
|
||||||
|
logger := gojs.GetLogger(vm)
|
||||||
|
|
||||||
|
// 连接服务器
|
||||||
|
addr := fmt.Sprintf("%s:%d", obj.imapHost, obj.imapPort)
|
||||||
|
imapClient, err := imapclient.DialTLS(addr, nil)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("failed to connect to IMAP server", "addr", addr, "err", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送客户端标识
|
||||||
|
imapClient.ID(&imap.IDData{
|
||||||
|
Name: conf.ClientName,
|
||||||
|
Version: conf.ClientVersion,
|
||||||
|
Vendor: conf.ClientVendor,
|
||||||
|
SupportURL: conf.ClientSupportURL,
|
||||||
|
OS: conf.ClientOS,
|
||||||
|
})
|
||||||
|
|
||||||
|
// 登录
|
||||||
|
if err := imapClient.Login(obj.username, obj.password).Wait(); err != nil {
|
||||||
|
logger.Error("IMAP login failed", "user", obj.username, "err", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &IMAPClient{client: imapClient}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (obj *IMAPClient) ListMailbox(vm *goja.Runtime) ([]MailboxInfo, error) {
|
||||||
|
logger := gojs.GetLogger(vm)
|
||||||
|
mailboxes, err := obj.client.List("", "%", nil).Collect()
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("failed to list mailboxes", "err", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
list := []MailboxInfo{}
|
||||||
|
for _, mbox := range mailboxes {
|
||||||
|
// fmt.Println(u.JsonP(mbox), 111)
|
||||||
|
attrs := []string{}
|
||||||
|
for _, attr := range mbox.Attrs {
|
||||||
|
attrs = append(attrs, string(attr))
|
||||||
|
}
|
||||||
|
list = append(list, MailboxInfo{
|
||||||
|
Name: mbox.Mailbox,
|
||||||
|
Delimiter: string(mbox.Delim),
|
||||||
|
Attrs: attrs,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return list, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (obj *IMAPClient) Recv(vm *goja.Runtime, opt *RecvOption) (*RecvResult, error) {
|
||||||
|
logger := gojs.GetLogger(vm)
|
||||||
|
|
||||||
|
// 创建默认选项
|
||||||
|
if opt == nil {
|
||||||
|
opt = &RecvOption{
|
||||||
|
Mailbox: "INBOX",
|
||||||
|
Unseen: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rr := &RecvResult{Tag: opt.Tag, List: []Mail{}}
|
||||||
|
|
||||||
|
// 设置默认限制
|
||||||
|
if opt.Limit == 0 {
|
||||||
|
opt.Limit = 20
|
||||||
|
}
|
||||||
|
|
||||||
|
// 选择邮箱
|
||||||
|
mailboxName := opt.Mailbox
|
||||||
|
if mailboxName == "" {
|
||||||
|
mailboxName = "INBOX"
|
||||||
|
}
|
||||||
|
|
||||||
|
// 选择邮箱
|
||||||
|
c := obj.client
|
||||||
|
selectCmd := c.Select(mailboxName, nil)
|
||||||
|
_, err := selectCmd.Wait()
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("failed to select mailbox", "mailbox", mailboxName, "err", err)
|
||||||
|
return rr, err
|
||||||
|
}
|
||||||
|
|
||||||
|
limit := imap.UID(opt.Limit)
|
||||||
|
startUID := imap.UID(opt.Tag) + 1 // 从上一次最大UID+1开始
|
||||||
|
endUID := imap.UID(math.MaxUint32) // 始终使用最大值,确保搜索时能拿到足够的结果
|
||||||
|
criteria := &imap.SearchCriteria{
|
||||||
|
UID: []imap.UIDSet{[]imap.UIDRange{{Start: startUID, Stop: endUID}}},
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加基本搜索条件
|
||||||
|
if opt.Unseen {
|
||||||
|
criteria.NotFlag = append(criteria.NotFlag, imap.FlagSeen)
|
||||||
|
}
|
||||||
|
if opt.Read {
|
||||||
|
criteria.Flag = append(criteria.Flag, imap.FlagSeen)
|
||||||
|
}
|
||||||
|
if opt.Answered {
|
||||||
|
criteria.Flag = append(criteria.Flag, imap.FlagAnswered)
|
||||||
|
}
|
||||||
|
if opt.Flagged {
|
||||||
|
criteria.Flag = append(criteria.Flag, imap.FlagFlagged)
|
||||||
|
}
|
||||||
|
if opt.Deleted {
|
||||||
|
criteria.Flag = append(criteria.Flag, imap.FlagDeleted)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加时间范围条件
|
||||||
|
if opt.Since != "" {
|
||||||
|
criteria.Since = u.ParseTime(opt.Since)
|
||||||
|
}
|
||||||
|
if opt.Before != "" {
|
||||||
|
criteria.Before = u.ParseTime(opt.Before)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加自定义搜索条件
|
||||||
|
if len(opt.Search) > 0 {
|
||||||
|
criteria.Text = opt.Search
|
||||||
|
}
|
||||||
|
if len(opt.Body) > 0 {
|
||||||
|
criteria.Body = opt.Body
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(opt.Subject) > 0 {
|
||||||
|
for _, word := range opt.Subject {
|
||||||
|
criteria.Header = append(criteria.Header, imap.SearchCriteriaHeaderField{
|
||||||
|
Key: "Subject",
|
||||||
|
Value: word,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(opt.From) > 0 {
|
||||||
|
for _, word := range opt.From {
|
||||||
|
criteria.Header = append(criteria.Header, imap.SearchCriteriaHeaderField{
|
||||||
|
Key: "From",
|
||||||
|
Value: word,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(opt.To) > 0 {
|
||||||
|
for _, word := range opt.To {
|
||||||
|
criteria.Header = append(criteria.Header, imap.SearchCriteriaHeaderField{
|
||||||
|
Key: "To",
|
||||||
|
Value: word,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(opt.Cc) > 0 {
|
||||||
|
for _, word := range opt.Cc {
|
||||||
|
criteria.Header = append(criteria.Header, imap.SearchCriteriaHeaderField{
|
||||||
|
Key: "Cc",
|
||||||
|
Value: word,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(opt.Bcc) > 0 {
|
||||||
|
for _, word := range opt.Bcc {
|
||||||
|
criteria.Header = append(criteria.Header, imap.SearchCriteriaHeaderField{
|
||||||
|
Key: "Bcc",
|
||||||
|
Value: word,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(opt.Not) > 0 {
|
||||||
|
criteria.Not = []imap.SearchCriteria{}
|
||||||
|
criteria.Not = append(criteria.Not, imap.SearchCriteria{
|
||||||
|
Text: opt.Not,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := c.UIDSearch(criteria, nil).Wait()
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("failed to search messages", "err", err)
|
||||||
|
return rr, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var uidSet imap.UIDSet
|
||||||
|
maxUID := startUID
|
||||||
|
var uidCount imap.UID
|
||||||
|
if all, ok := r.All.(imap.UIDSet); ok {
|
||||||
|
for _, set := range all {
|
||||||
|
n := set.Stop - set.Start
|
||||||
|
if uidCount+n > limit {
|
||||||
|
set.Stop -= (uidCount + n - limit)
|
||||||
|
}
|
||||||
|
maxUID = set.Stop
|
||||||
|
uidCount += set.Stop - set.Start
|
||||||
|
uidSet = append(uidSet, set)
|
||||||
|
if uidCount >= limit {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(uidSet) == 0 {
|
||||||
|
return rr, nil
|
||||||
|
}
|
||||||
|
fr, err := c.Fetch(uidSet, &imap.FetchOptions{Envelope: true, BodySection: []*imap.FetchItemBodySection{{}}}).Collect()
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("failed to fetch messages", "err", err)
|
||||||
|
return rr, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, msg := range fr {
|
||||||
|
mail := Mail{
|
||||||
|
Uid: uint32(msg.UID),
|
||||||
|
Subject: msg.Envelope.Subject,
|
||||||
|
From: FormatAddressList(msg.Envelope.From),
|
||||||
|
To: FormatAddressList(msg.Envelope.To),
|
||||||
|
Cc: FormatAddressList(msg.Envelope.Cc),
|
||||||
|
Bcc: FormatAddressList(msg.Envelope.Bcc),
|
||||||
|
Date: msg.Envelope.Date.Format(time.DateTime),
|
||||||
|
Size: msg.RFC822Size,
|
||||||
|
Flags: FormatFlags(msg.Flags),
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析邮件正文和附件
|
||||||
|
// fmt.Println("####0", len(msg.BodySection))
|
||||||
|
for _, section := range msg.BodySection {
|
||||||
|
if section.Section != nil && section.Section.Specifier == imap.PartSpecifierNone {
|
||||||
|
// fmt.Println("####1", len(section.Bytes))
|
||||||
|
env, err := enmime.ReadEnvelope(bytes.NewReader(section.Bytes))
|
||||||
|
if err == nil {
|
||||||
|
mail.Text = env.Text
|
||||||
|
mail.Html = env.HTML
|
||||||
|
|
||||||
|
// 处理附件
|
||||||
|
for _, attach := range env.Attachments {
|
||||||
|
mail.Attachments = append(mail.Attachments, Attachment{
|
||||||
|
Name: attach.FileName,
|
||||||
|
Data: u.Base64(attach.Content),
|
||||||
|
ContentType: attach.ContentType,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理内嵌资源
|
||||||
|
for _, embed := range env.Inlines {
|
||||||
|
mail.Embeds = append(mail.Embeds, Attachment{
|
||||||
|
Name: embed.FileName,
|
||||||
|
Data: u.Base64(embed.Content),
|
||||||
|
ContentType: embed.ContentType,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.Error("failed to read envelope", "err", err)
|
||||||
|
return rr, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rr.List = append(rr.List, mail)
|
||||||
|
}
|
||||||
|
|
||||||
|
rr.Tag = uint32(maxUID)
|
||||||
|
return rr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func FormatFlags(flags []imap.Flag) []string {
|
||||||
|
result := make([]string, len(flags))
|
||||||
|
for i, flag := range flags {
|
||||||
|
result[i] = string(flag)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseFlags(flags []string) []imap.Flag {
|
||||||
|
result := make([]imap.Flag, len(flags))
|
||||||
|
for i, flag := range flags {
|
||||||
|
result[i] = imap.Flag(flag)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// 地址格式化
|
||||||
|
func FormatAddress(addr *imap.Address) string {
|
||||||
|
if addr == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if addr.Name != "" {
|
||||||
|
return fmt.Sprintf("%s <%s>", addr.Name, addr.Addr())
|
||||||
|
}
|
||||||
|
return addr.Addr()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 地址列表格式化
|
||||||
|
func FormatAddressList(addrs []imap.Address) []string {
|
||||||
|
result := make([]string, len(addrs))
|
||||||
|
for i, addr := range addrs {
|
||||||
|
result[i] = FormatAddress(&addr)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
65
mail.go
Normal file
65
mail.go
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
package mail
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/mail"
|
||||||
|
|
||||||
|
"apigo.cc/gojs/goja"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Mailbox struct {
|
||||||
|
smtpHost string
|
||||||
|
smtpPort int
|
||||||
|
imapHost string
|
||||||
|
imapPort int
|
||||||
|
username string
|
||||||
|
password string
|
||||||
|
senderName string
|
||||||
|
senderMail string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Attachment struct {
|
||||||
|
Path string
|
||||||
|
Name string
|
||||||
|
Data string
|
||||||
|
ContentType string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (obj *Mailbox) Recv(vm *goja.Runtime, opt *RecvOption) (*RecvResult, error) {
|
||||||
|
c, err := obj.OpenIMAP(vm)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer c.Close(vm)
|
||||||
|
return c.Recv(vm, opt)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (obj *Mailbox) ParseAddr(addr string) (name, address string, err error) {
|
||||||
|
r, err := mail.ParseAddress(addr)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
return r.Name, r.Address, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (obj *Mailbox) ParseAddrList(addrs []string) ([]map[string]string, error) {
|
||||||
|
result := make([]map[string]string, len(addrs))
|
||||||
|
for i, addr := range addrs {
|
||||||
|
name, address, err := obj.ParseAddr(addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
result[i] = map[string]string{
|
||||||
|
"name": name,
|
||||||
|
"address": address,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (obj *Mailbox) FormatAddr(name, address string) string {
|
||||||
|
addr := &mail.Address{
|
||||||
|
Name: name,
|
||||||
|
Address: address,
|
||||||
|
}
|
||||||
|
return addr.String()
|
||||||
|
}
|
31
mail_test.js
Normal file
31
mail_test.js
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import mail from 'apigo.cc/gojs/mail'
|
||||||
|
import co from 'apigo.cc/gojs/console'
|
||||||
|
|
||||||
|
// mail.send(['star3s@126.com'], '测试邮件', '这是一个测试邮件<br/><img src="cid:1.png"/> !!', {
|
||||||
|
// cc: ['isstar3s@126.com'],
|
||||||
|
// html: true,
|
||||||
|
// embeds: [{ name: '1.png', data: 'iVBORw0KGgoAAAANSUhEUgAAABEAAAATCAMAAABBexbDAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAFZaVRYdFhNTDpjb20uYWRvYmUueG1wAAAAAAA8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJYTVAgQ29yZSA2LjAuMCI+CiAgIDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+CiAgICAgIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICAgICAgICAgIHhtbG5zOnRpZmY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vdGlmZi8xLjAvIj4KICAgICAgICAgPHRpZmY6T3JpZW50YXRpb24+MTwvdGlmZjpPcmllbnRhdGlvbj4KICAgICAgPC9yZGY6RGVzY3JpcHRpb24+CiAgIDwvcmRmOlJERj4KPC94OnhtcG1ldGE+Chle4QcAAABjUExURaOju6yiu6ajtqKlt7ijttenuaCmvcipuOezvKStwbW2yqSgudSsuNyyuaSpvZeasua3uqWjtcSwv9XR6MS80ei0ys3H3audsLKswruqu8qjvOKrxOKtudOwveTe8+i3xMXC2TWaOysAAADFSURBVBjTRc7tssMQEAZgqW8VFBVEmtz/VZ5dOZ2+M368j92BEMzjdecBmSAI/dEEIegdKM8bxLeT5xStqXOUign/4pyDQxYMitMG4xYgtoFooU0q2VQYYIyhmHx9PgmIMSk7bl2poMTWmOQcpJqSUrpyjBJg4AdqLqVk6NbyoUCWWluMh4X7NQSFD7YJYwylQljJwnrH+VVhDisJY51z2A+wsB4Sfth757s6T5i30DdPON/39/s85/3mvSfYIYdsL+ze/wG5Qw7KZj0DQwAAAABJRU5ErkJggg==' }],
|
||||||
|
// })
|
||||||
|
|
||||||
|
// let imap = mail.openIMAP()
|
||||||
|
// let boxes = imap.listMailbox()
|
||||||
|
// co.info(boxes)
|
||||||
|
// imap.close()
|
||||||
|
|
||||||
|
let r = mail.recv({
|
||||||
|
limit: 1,
|
||||||
|
// subject: ['审核'],
|
||||||
|
from: ['测试工程师'],
|
||||||
|
// search: ['腾讯云备案管局审核通过通知'],
|
||||||
|
// not: ['审核'],
|
||||||
|
})
|
||||||
|
co.info(r)
|
||||||
|
|
||||||
|
// r = mail.recv({
|
||||||
|
// limit: 5,
|
||||||
|
// tag: r.tag,
|
||||||
|
// })
|
||||||
|
// co.info(r)
|
||||||
|
|
||||||
|
|
||||||
|
return true
|
221
plugin.go
221
plugin.go
@ -1,40 +1,68 @@
|
|||||||
package plugin
|
package mail
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"runtime"
|
||||||
"encoding/base64"
|
|
||||||
"io"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"apigo.cc/gojs"
|
"apigo.cc/gojs"
|
||||||
"apigo.cc/gojs/goja"
|
"apigo.cc/gojs/goja"
|
||||||
"github.com/jaytaylor/html2text"
|
|
||||||
"github.com/ssgo/config"
|
"github.com/ssgo/config"
|
||||||
|
"github.com/ssgo/log"
|
||||||
"github.com/ssgo/u"
|
"github.com/ssgo/u"
|
||||||
"gopkg.in/gomail.v2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const pluginName = "mail"
|
const pluginName = "mail"
|
||||||
|
|
||||||
var defaultObjectName = "default"
|
type Config struct {
|
||||||
|
Mailbox map[string]*MailboxConfig
|
||||||
|
ClientName string
|
||||||
|
ClientVersion string
|
||||||
|
ClientOS string
|
||||||
|
ClientVendor string
|
||||||
|
ClientSupportURL string
|
||||||
|
}
|
||||||
|
|
||||||
|
type MailboxConfig struct {
|
||||||
|
SmtpHost string
|
||||||
|
SmtpPort int
|
||||||
|
ImapHost string
|
||||||
|
ImapPort int
|
||||||
|
Username string
|
||||||
|
Password string
|
||||||
|
SenderName string
|
||||||
|
SenderMail string
|
||||||
|
}
|
||||||
|
|
||||||
|
var conf = &Config{
|
||||||
|
Mailbox: make(map[string]*MailboxConfig),
|
||||||
|
ClientName: "Apigo Mail Client",
|
||||||
|
ClientVersion: "1.0.0",
|
||||||
|
ClientOS: runtime.GOOS,
|
||||||
|
ClientVendor: "apigo.cc",
|
||||||
|
ClientSupportURL: "https://apigo.cc/gojs/mail",
|
||||||
|
}
|
||||||
|
|
||||||
var defaultObject = &DefaultObject{}
|
var defaultObject = &DefaultObject{}
|
||||||
var confAes *u.Aes = u.NewAes([]byte("?GQ$0K0GgLdO=f+~L68PLm$uhKr4'=tV"), []byte("VFs7@sK61cj^f?HZ"))
|
var confAes *u.Aes = u.NewAes([]byte("?GQ$0K0GgLdO=f+~L68PLm$uhKr4'=tV"), []byte("VFs7@sK61cj^f?HZ"))
|
||||||
var objects = make(map[string]*Object)
|
var objects = make(map[string]*Mailbox)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
tsCode := gojs.MakeTSCode(defaultObject)
|
tsCode := gojs.MakeTSCode(defaultObject)
|
||||||
gojs.Register("apigo.cc/gojs/"+pluginName, gojs.Module{
|
gojs.Register("apigo.cc/gojs/"+pluginName, gojs.Module{
|
||||||
ObjectMaker: func(vm *goja.Runtime) gojs.Map {
|
ObjectMaker: func(vm *goja.Runtime) gojs.Map {
|
||||||
configs := make(map[string]*Config)
|
config.LoadConfig(pluginName, conf)
|
||||||
config.LoadConfig(pluginName, &configs)
|
for name, object := range conf.Mailbox {
|
||||||
for name, object := range configs {
|
objects[name] = defaultObject.New(object)
|
||||||
objects[name] = defaultObject.New(*object)
|
}
|
||||||
|
if defaultMailbox, err := defaultObject.Get("default"); err == nil {
|
||||||
|
defaultObject.Mailbox = defaultMailbox
|
||||||
|
} else if len(objects) == 0 {
|
||||||
|
log.DefaultLogger.Warning("no mailbox configured", "example for env.yml", "mail:\n mailbox:\n default:\n username: ****@***.***")
|
||||||
}
|
}
|
||||||
defaultObject.Object = defaultObject.GetInstance("default")
|
|
||||||
return gojs.ToMap(defaultObject)
|
return gojs.ToMap(defaultObject)
|
||||||
},
|
},
|
||||||
TsCode: tsCode,
|
TsCode: tsCode,
|
||||||
Desc: "mail api by github.com/ssgo/mail",
|
Desc: "mail api",
|
||||||
SetSSKey: func(key, iv []byte) {
|
SetSSKey: func(key, iv []byte) {
|
||||||
confAes = u.NewAes(key, iv)
|
confAes = u.NewAes(key, iv)
|
||||||
},
|
},
|
||||||
@ -42,18 +70,18 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type DefaultObject struct {
|
type DefaultObject struct {
|
||||||
*Object
|
*Mailbox
|
||||||
}
|
}
|
||||||
|
|
||||||
func (obj *DefaultObject) GetInstance(name string) *Object {
|
func (obj *DefaultObject) Get(name string) (*Mailbox, error) {
|
||||||
if o, ok := objects[name]; ok {
|
if o, ok := objects[name]; ok {
|
||||||
return o
|
return o, nil
|
||||||
} else {
|
} else {
|
||||||
return obj.Object
|
return nil, gojs.Err("mailbox [" + name + "] not configured")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (obj *DefaultObject) New(conf Config) *Object {
|
func (obj *DefaultObject) New(conf *MailboxConfig) *Mailbox {
|
||||||
a := strings.SplitN(conf.Username, "@", 2)
|
a := strings.SplitN(conf.Username, "@", 2)
|
||||||
if len(a) == 2 {
|
if len(a) == 2 {
|
||||||
if conf.SmtpHost == "" {
|
if conf.SmtpHost == "" {
|
||||||
@ -69,8 +97,11 @@ func (obj *DefaultObject) New(conf Config) *Object {
|
|||||||
conf.ImapPort = 993
|
conf.ImapPort = 993
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if conf.SenderMail == "" {
|
||||||
|
conf.SenderMail = conf.Username
|
||||||
|
}
|
||||||
|
|
||||||
return &Object{
|
return &Mailbox{
|
||||||
smtpHost: conf.SmtpHost,
|
smtpHost: conf.SmtpHost,
|
||||||
smtpPort: conf.SmtpPort,
|
smtpPort: conf.SmtpPort,
|
||||||
imapHost: conf.ImapHost,
|
imapHost: conf.ImapHost,
|
||||||
@ -78,154 +109,6 @@ func (obj *DefaultObject) New(conf Config) *Object {
|
|||||||
username: conf.Username,
|
username: conf.Username,
|
||||||
password: confAes.DecryptUrlBase64ToString(conf.Password),
|
password: confAes.DecryptUrlBase64ToString(conf.Password),
|
||||||
senderName: conf.SenderName,
|
senderName: conf.SenderName,
|
||||||
|
senderMail: conf.SenderMail,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type Config struct {
|
|
||||||
SmtpHost string
|
|
||||||
SmtpPort int
|
|
||||||
ImapHost string
|
|
||||||
ImapPort int
|
|
||||||
Username string
|
|
||||||
Password string
|
|
||||||
SenderName string
|
|
||||||
}
|
|
||||||
|
|
||||||
type Object struct {
|
|
||||||
smtpHost string
|
|
||||||
smtpPort int
|
|
||||||
imapHost string
|
|
||||||
imapPort int
|
|
||||||
username string
|
|
||||||
password string
|
|
||||||
senderName string
|
|
||||||
}
|
|
||||||
|
|
||||||
type Attachment struct {
|
|
||||||
Path string
|
|
||||||
Name string
|
|
||||||
Data string
|
|
||||||
ContentType string
|
|
||||||
}
|
|
||||||
|
|
||||||
type SendOption struct {
|
|
||||||
Cc []string
|
|
||||||
Bcc []string
|
|
||||||
Html bool
|
|
||||||
Attachments []Attachment
|
|
||||||
Embeds []Attachment
|
|
||||||
}
|
|
||||||
|
|
||||||
func (obj *Object) Send(vm *goja.Runtime, to []string, subject, content string, option *SendOption) error {
|
|
||||||
logger := gojs.GetLogger(vm)
|
|
||||||
|
|
||||||
msg := gomail.NewMessage()
|
|
||||||
if obj.senderName != "" {
|
|
||||||
msg.SetHeader("From", msg.FormatAddress(obj.username, obj.senderName))
|
|
||||||
} else {
|
|
||||||
msg.SetHeader("From", obj.username)
|
|
||||||
}
|
|
||||||
|
|
||||||
msg.SetHeader("To", to...)
|
|
||||||
msg.SetHeader("Subject", subject)
|
|
||||||
useHtml := false
|
|
||||||
if option != nil {
|
|
||||||
if len(option.Cc) > 0 {
|
|
||||||
msg.SetHeader("Cc", option.Cc...)
|
|
||||||
}
|
|
||||||
if len(option.Bcc) > 0 {
|
|
||||||
msg.SetHeader("Bcc", option.Bcc...)
|
|
||||||
}
|
|
||||||
if len(option.Attachments) > 0 {
|
|
||||||
for _, attachment := range option.Attachments {
|
|
||||||
if attachment.Path != "" {
|
|
||||||
if u.FileExists(attachment.Path) {
|
|
||||||
msg.Attach(attachment.Path)
|
|
||||||
} else {
|
|
||||||
logger.Error("mail attachment file not exists", "filename", attachment.Path, "to", to, "subject", subject)
|
|
||||||
}
|
|
||||||
} else if attachment.Data != "" {
|
|
||||||
if attachment.Name == "" {
|
|
||||||
attachment.Name = "attachment"
|
|
||||||
}
|
|
||||||
if attachment.ContentType == "" {
|
|
||||||
attachment.ContentType = "text/plain"
|
|
||||||
}
|
|
||||||
buf, err := base64.StdEncoding.DecodeString(attachment.Data)
|
|
||||||
if err != nil {
|
|
||||||
buf = []byte(attachment.Data)
|
|
||||||
}
|
|
||||||
|
|
||||||
msg.Attach(attachment.Name, gomail.SetCopyFunc(func(w io.Writer) error {
|
|
||||||
_, err := w.Write(buf)
|
|
||||||
return err
|
|
||||||
}), gomail.SetHeader(map[string][]string{
|
|
||||||
"Content-Type": {attachment.ContentType},
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(option.Embeds) > 0 {
|
|
||||||
for _, attachment := range option.Embeds {
|
|
||||||
if attachment.Path != "" {
|
|
||||||
if u.FileExists(attachment.Path) {
|
|
||||||
msg.Attach(attachment.Path)
|
|
||||||
} else {
|
|
||||||
logger.Error("mail embed file not exists", "filename", attachment.Path, "to", to, "subject", subject)
|
|
||||||
}
|
|
||||||
} else if attachment.Data != "" {
|
|
||||||
if attachment.Name == "" {
|
|
||||||
attachment.Name = "image"
|
|
||||||
}
|
|
||||||
if attachment.ContentType == "" {
|
|
||||||
attachment.ContentType = "image/png"
|
|
||||||
}
|
|
||||||
buf, err := base64.StdEncoding.DecodeString(attachment.Data)
|
|
||||||
if err != nil {
|
|
||||||
buf = []byte(attachment.Data)
|
|
||||||
}
|
|
||||||
|
|
||||||
msg.Embed(attachment.Name, gomail.SetCopyFunc(func(w io.Writer) error {
|
|
||||||
_, err := w.Write(buf)
|
|
||||||
return err
|
|
||||||
}), gomail.SetHeader(map[string][]string{
|
|
||||||
"Content-Type": {attachment.ContentType},
|
|
||||||
"Content-ID": {"<" + attachment.Name + ">"},
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
useHtml = option.Html
|
|
||||||
}
|
|
||||||
if useHtml {
|
|
||||||
msg.SetBody("text/html", content)
|
|
||||||
textContent, err := html2text.FromString(content, html2text.Options{TextOnly: true, PrettyTables: true})
|
|
||||||
if err != nil {
|
|
||||||
logger.Error("convert html to text failed", "err", err, "to", to, "subject", subject, "content", content)
|
|
||||||
textContent = content
|
|
||||||
}
|
|
||||||
msg.AddAlternative("text/plain", textContent)
|
|
||||||
} else {
|
|
||||||
msg.SetBody("text/plain", content)
|
|
||||||
}
|
|
||||||
|
|
||||||
conn := gomail.NewDialer(obj.smtpHost, obj.smtpPort, obj.username, obj.password)
|
|
||||||
switch obj.smtpPort {
|
|
||||||
case 465:
|
|
||||||
conn.SSL = true
|
|
||||||
case 587:
|
|
||||||
conn.TLSConfig = &tls.Config{
|
|
||||||
ServerName: obj.smtpHost,
|
|
||||||
InsecureSkipVerify: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// conn.Auth = smtp.PlainAuth("", obj.username, obj.password, obj.smtpHost)
|
|
||||||
|
|
||||||
err := conn.DialAndSend(msg)
|
|
||||||
if err != nil {
|
|
||||||
logger.Error("send mail failed", "err", err.Error(), "to", to, "subject", "from", obj.username, subject, "content", content)
|
|
||||||
} else {
|
|
||||||
logger.Info("send mail success", "to", to, "from", obj.username, "subject", subject)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
@ -1,16 +1,65 @@
|
|||||||
package plugin_test
|
package mail_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"apigo.cc/gojs"
|
"apigo.cc/gojs"
|
||||||
|
_ "apigo.cc/gojs/console"
|
||||||
"github.com/ssgo/u"
|
"github.com/ssgo/u"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestPlugin(t *testing.T) {
|
func TestPlugin(t *testing.T) {
|
||||||
|
// c, err := imapclient.DialTLS("imap.126.com:993", nil)
|
||||||
|
// if err != nil {
|
||||||
|
// log.Fatalf("failed to dial IMAP server: %v", err)
|
||||||
|
// }
|
||||||
|
// defer c.Close()
|
||||||
|
|
||||||
|
// c.ID(&imap.IDData{
|
||||||
|
// Name: "Apigo Mail Client",
|
||||||
|
// Version: "1.0.0",
|
||||||
|
// Vendor: "apigo.cc",
|
||||||
|
// SupportURL: "https://apigo.cc/gojs/mail",
|
||||||
|
// OS: runtime.GOOS,
|
||||||
|
// })
|
||||||
|
|
||||||
|
// if err := c.Login("api_go@126.com", "ZZEQWMBONBXZGVKY").Wait(); err != nil {
|
||||||
|
// log.Fatalf("failed to login: %v", err)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// mailboxes, err := c.List("", "%", nil).Collect()
|
||||||
|
// if err != nil {
|
||||||
|
// log.Fatalf("failed to list mailboxes: %v", err)
|
||||||
|
// }
|
||||||
|
// log.Printf("Found %v mailboxes", len(mailboxes))
|
||||||
|
// for _, mbox := range mailboxes {
|
||||||
|
// log.Printf(" - %v", mbox.Mailbox)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// selectedMbox, err := c.Select("INBOX", nil).Wait()
|
||||||
|
// if err != nil {
|
||||||
|
// log.Fatalf("failed to select INBOX: %v", err)
|
||||||
|
// }
|
||||||
|
// log.Printf("INBOX contains %v messages", selectedMbox.NumMessages)
|
||||||
|
|
||||||
|
// if selectedMbox.NumMessages > 0 {
|
||||||
|
// seqSet := imap.SeqSetNum(1)
|
||||||
|
// fetchOptions := &imap.FetchOptions{Envelope: true}
|
||||||
|
// messages, err := c.Fetch(seqSet, fetchOptions).Collect()
|
||||||
|
// if err != nil {
|
||||||
|
// log.Fatalf("failed to fetch first message in INBOX: %v", err)
|
||||||
|
// }
|
||||||
|
// log.Printf("subject of first message in INBOX: %v", messages[0].Envelope.Subject)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if err := c.Logout().Wait(); err != nil {
|
||||||
|
// log.Fatalf("failed to logout: %v", err)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return
|
||||||
gojs.ExportForDev()
|
gojs.ExportForDev()
|
||||||
r, err := gojs.RunFile("plugin_test.js")
|
r, err := gojs.RunFile("mail_test.js")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
} else if r != true {
|
} else if r != true {
|
||||||
|
@ -1,9 +0,0 @@
|
|||||||
import mail from 'apigo.cc/gojs/mail'
|
|
||||||
|
|
||||||
mail.send(['star3s@126.com'], '测试邮件', '这是一个测试邮件<br/><img src="cid:1.png"/> !!', {
|
|
||||||
cc: ['isstar3s@126.com'],
|
|
||||||
html: true,
|
|
||||||
embeds: [{ name: '1.png', data: 'iVBORw0KGgoAAAANSUhEUgAAABEAAAATCAMAAABBexbDAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAFZaVRYdFhNTDpjb20uYWRvYmUueG1wAAAAAAA8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJYTVAgQ29yZSA2LjAuMCI+CiAgIDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+CiAgICAgIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICAgICAgICAgIHhtbG5zOnRpZmY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vdGlmZi8xLjAvIj4KICAgICAgICAgPHRpZmY6T3JpZW50YXRpb24+MTwvdGlmZjpPcmllbnRhdGlvbj4KICAgICAgPC9yZGY6RGVzY3JpcHRpb24+CiAgIDwvcmRmOlJERj4KPC94OnhtcG1ldGE+Chle4QcAAABjUExURaOju6yiu6ajtqKlt7ijttenuaCmvcipuOezvKStwbW2yqSgudSsuNyyuaSpvZeasua3uqWjtcSwv9XR6MS80ei0ys3H3audsLKswruqu8qjvOKrxOKtudOwveTe8+i3xMXC2TWaOysAAADFSURBVBjTRc7tssMQEAZgqW8VFBVEmtz/VZ5dOZ2+M368j92BEMzjdecBmSAI/dEEIegdKM8bxLeT5xStqXOUign/4pyDQxYMitMG4xYgtoFooU0q2VQYYIyhmHx9PgmIMSk7bl2poMTWmOQcpJqSUrpyjBJg4AdqLqVk6NbyoUCWWluMh4X7NQSFD7YJYwylQljJwnrH+VVhDisJY51z2A+wsB4Sfth757s6T5i30DdPON/39/s85/3mvSfYIYdsL+ze/wG5Qw7KZj0DQwAAAABJRU5ErkJggg==' }],
|
|
||||||
})
|
|
||||||
|
|
||||||
return true
|
|
135
smtp.go
Normal file
135
smtp.go
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
package mail
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"encoding/base64"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"apigo.cc/gojs"
|
||||||
|
"apigo.cc/gojs/goja"
|
||||||
|
"github.com/jaytaylor/html2text"
|
||||||
|
"github.com/ssgo/u"
|
||||||
|
"gopkg.in/gomail.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SendOption struct {
|
||||||
|
Cc []string
|
||||||
|
Bcc []string
|
||||||
|
Html bool
|
||||||
|
Attachments []Attachment
|
||||||
|
Embeds []Attachment
|
||||||
|
}
|
||||||
|
|
||||||
|
func (obj *Mailbox) Send(vm *goja.Runtime, to []string, subject, content string, option *SendOption) error {
|
||||||
|
logger := gojs.GetLogger(vm)
|
||||||
|
|
||||||
|
msg := gomail.NewMessage()
|
||||||
|
if obj.senderName != "" {
|
||||||
|
msg.SetHeader("From", msg.FormatAddress(obj.username, obj.senderName))
|
||||||
|
} else {
|
||||||
|
msg.SetHeader("From", obj.username)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.SetHeader("To", to...)
|
||||||
|
msg.SetHeader("Subject", subject)
|
||||||
|
useHtml := false
|
||||||
|
if option != nil {
|
||||||
|
if len(option.Cc) > 0 {
|
||||||
|
msg.SetHeader("Cc", option.Cc...)
|
||||||
|
}
|
||||||
|
if len(option.Bcc) > 0 {
|
||||||
|
msg.SetHeader("Bcc", option.Bcc...)
|
||||||
|
}
|
||||||
|
if len(option.Attachments) > 0 {
|
||||||
|
for _, attachment := range option.Attachments {
|
||||||
|
if attachment.Path != "" {
|
||||||
|
if u.FileExists(attachment.Path) {
|
||||||
|
msg.Attach(attachment.Path)
|
||||||
|
} else {
|
||||||
|
logger.Error("mail attachment file not exists", "filename", attachment.Path, "to", to, "subject", subject)
|
||||||
|
}
|
||||||
|
} else if attachment.Data != "" {
|
||||||
|
if attachment.Name == "" {
|
||||||
|
attachment.Name = "attachment"
|
||||||
|
}
|
||||||
|
if attachment.ContentType == "" {
|
||||||
|
attachment.ContentType = "text/plain"
|
||||||
|
}
|
||||||
|
buf, err := base64.StdEncoding.DecodeString(attachment.Data)
|
||||||
|
if err != nil {
|
||||||
|
buf = []byte(attachment.Data)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.Attach(attachment.Name, gomail.SetCopyFunc(func(w io.Writer) error {
|
||||||
|
_, err := w.Write(buf)
|
||||||
|
return err
|
||||||
|
}), gomail.SetHeader(map[string][]string{
|
||||||
|
"Content-Type": {attachment.ContentType},
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(option.Embeds) > 0 {
|
||||||
|
for _, attachment := range option.Embeds {
|
||||||
|
if attachment.Path != "" {
|
||||||
|
if u.FileExists(attachment.Path) {
|
||||||
|
msg.Attach(attachment.Path)
|
||||||
|
} else {
|
||||||
|
logger.Error("mail embed file not exists", "filename", attachment.Path, "to", to, "subject", subject)
|
||||||
|
}
|
||||||
|
} else if attachment.Data != "" {
|
||||||
|
if attachment.Name == "" {
|
||||||
|
attachment.Name = "image"
|
||||||
|
}
|
||||||
|
if attachment.ContentType == "" {
|
||||||
|
attachment.ContentType = "image/png"
|
||||||
|
}
|
||||||
|
buf, err := base64.StdEncoding.DecodeString(attachment.Data)
|
||||||
|
if err != nil {
|
||||||
|
buf = []byte(attachment.Data)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.Embed(attachment.Name, gomail.SetCopyFunc(func(w io.Writer) error {
|
||||||
|
_, err := w.Write(buf)
|
||||||
|
return err
|
||||||
|
}), gomail.SetHeader(map[string][]string{
|
||||||
|
"Content-Type": {attachment.ContentType},
|
||||||
|
"Content-ID": {"<" + attachment.Name + ">"},
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
useHtml = option.Html
|
||||||
|
}
|
||||||
|
if useHtml {
|
||||||
|
msg.SetBody("text/html", content)
|
||||||
|
textContent, err := html2text.FromString(content, html2text.Options{TextOnly: true, PrettyTables: true})
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("convert html to text failed", "err", err, "to", to, "subject", subject, "content", content)
|
||||||
|
textContent = content
|
||||||
|
}
|
||||||
|
msg.AddAlternative("text/plain", textContent)
|
||||||
|
} else {
|
||||||
|
msg.SetBody("text/plain", content)
|
||||||
|
}
|
||||||
|
|
||||||
|
conn := gomail.NewDialer(obj.smtpHost, obj.smtpPort, obj.username, obj.password)
|
||||||
|
switch obj.smtpPort {
|
||||||
|
case 465:
|
||||||
|
conn.SSL = true
|
||||||
|
case 587:
|
||||||
|
conn.TLSConfig = &tls.Config{
|
||||||
|
ServerName: obj.smtpHost,
|
||||||
|
InsecureSkipVerify: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// conn.Auth = smtp.PlainAuth("", obj.username, obj.password, obj.smtpHost)
|
||||||
|
|
||||||
|
err := conn.DialAndSend(msg)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("send mail failed", "err", err.Error(), "to", to, "subject", "from", obj.username, subject, "content", content)
|
||||||
|
} else {
|
||||||
|
logger.Info("send mail success", "to", to, "from", obj.username, "subject", subject)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user