Compare commits
No commits in common. "main" and "v0.0.1" have entirely different histories.
44
go.mod
44
go.mod
@ -1,38 +1,34 @@
|
|||||||
module apigo.cc/gojs/mail
|
module apigo.cc/gojs/mail
|
||||||
|
|
||||||
go 1.24.0
|
go 1.18
|
||||||
|
|
||||||
require (
|
require (
|
||||||
apigo.cc/gojs v0.0.30
|
apigo.cc/gojs v0.0.12
|
||||||
apigo.cc/gojs/console v0.0.4
|
apigo.cc/gojs/console v0.0.2
|
||||||
github.com/emersion/go-imap/v2 v2.0.0-beta.7
|
apigo.cc/gojs/redis v0.0.2
|
||||||
github.com/jaytaylor/html2text v0.0.0-20230321000545-74c2419ad056
|
github.com/ssgo/u v1.7.13
|
||||||
github.com/jhillyerd/enmime v1.3.0
|
|
||||||
github.com/ssgo/config v1.7.10
|
|
||||||
github.com/ssgo/log v1.7.9
|
|
||||||
github.com/ssgo/u v1.7.23
|
|
||||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
|
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a // indirect
|
github.com/dlclark/regexp2 v1.11.4 // indirect
|
||||||
github.com/dlclark/regexp2 v1.11.5 // indirect
|
github.com/emersion/go-imap v1.2.1 // indirect
|
||||||
github.com/emersion/go-message v0.18.1 // 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/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f // indirect
|
github.com/gomodule/redigo v1.9.2 // indirect
|
||||||
github.com/google/pprof v0.0.0-20250903194437-c28834ac2320 // indirect
|
github.com/google/pprof v0.0.0-20230207041349-798e818bf904 // indirect
|
||||||
github.com/mattn/go-runewidth v0.0.15 // indirect
|
github.com/jaytaylor/html2text v0.0.0-20230321000545-74c2419ad056 // indirect
|
||||||
|
github.com/mattn/go-runewidth v0.0.9 // indirect
|
||||||
github.com/olekukonko/tablewriter v0.0.5 // indirect
|
github.com/olekukonko/tablewriter v0.0.5 // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/ssgo/config v1.7.9 // indirect
|
||||||
github.com/rivo/uniseg v0.4.4 // indirect
|
github.com/ssgo/log v1.7.7 // indirect
|
||||||
|
github.com/ssgo/redis v1.7.7 // indirect
|
||||||
github.com/ssgo/standard v1.7.7 // indirect
|
github.com/ssgo/standard v1.7.7 // indirect
|
||||||
github.com/ssgo/tool v0.4.29 // indirect
|
github.com/ssgo/tool v0.4.28 // 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.47.0 // indirect
|
golang.org/x/net v0.32.0 // indirect
|
||||||
golang.org/x/sys v0.38.0 // indirect
|
golang.org/x/sys v0.28.0 // indirect
|
||||||
golang.org/x/text v0.31.0 // indirect
|
golang.org/x/text v0.21.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
370
imap.go
@ -1,370 +0,0 @@
|
|||||||
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
65
mail.go
@ -1,65 +0,0 @@
|
|||||||
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
31
mail_test.js
@ -1,31 +0,0 @@
|
|||||||
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
|
|
||||||
223
plugin.go
223
plugin.go
@ -1,67 +1,41 @@
|
|||||||
package mail
|
package plugin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"runtime"
|
"crypto/tls"
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"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/u"
|
"github.com/ssgo/u"
|
||||||
|
"gopkg.in/gomail.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
const pluginName = "mail"
|
const pluginName = "mail"
|
||||||
|
|
||||||
type Config struct {
|
var defaultObjectName = "default"
|
||||||
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]*Mailbox)
|
var objects = make(map[string]*Object)
|
||||||
|
|
||||||
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 {
|
||||||
config.LoadConfig(pluginName, conf)
|
configs := make(map[string]*Config)
|
||||||
for name, object := range conf.Mailbox {
|
config.LoadConfig(pluginName, &configs)
|
||||||
objects[name] = defaultObject.New(object)
|
for name, object := range configs {
|
||||||
}
|
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",
|
Desc: "mail api by github.com/ssgo/mail",
|
||||||
SetSSKey: func(key, iv []byte) {
|
SetSSKey: func(key, iv []byte) {
|
||||||
confAes = u.NewAes(key, iv)
|
confAes = u.NewAes(key, iv)
|
||||||
},
|
},
|
||||||
@ -69,18 +43,18 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type DefaultObject struct {
|
type DefaultObject struct {
|
||||||
*Mailbox
|
*Object
|
||||||
}
|
}
|
||||||
|
|
||||||
func (obj *DefaultObject) Get(name string) (*Mailbox, error) {
|
func (obj *DefaultObject) GetInstance(name string) *Object {
|
||||||
if o, ok := objects[name]; ok {
|
if o, ok := objects[name]; ok {
|
||||||
return o, nil
|
return o
|
||||||
} else {
|
} else {
|
||||||
return nil, gojs.Err("mailbox [" + name + "] not configured")
|
return obj.Object
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (obj *DefaultObject) New(conf *MailboxConfig) *Mailbox {
|
func (obj *DefaultObject) New(conf Config) *Object {
|
||||||
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 == "" {
|
||||||
@ -96,11 +70,8 @@ func (obj *DefaultObject) New(conf *MailboxConfig) *Mailbox {
|
|||||||
conf.ImapPort = 993
|
conf.ImapPort = 993
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if conf.SenderMail == "" {
|
fmt.Println(u.BMagenta(u.JsonP(conf)), 111)
|
||||||
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,
|
||||||
@ -108,6 +79,154 @@ func (obj *DefaultObject) New(conf *MailboxConfig) *Mailbox {
|
|||||||
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,65 +1,16 @@
|
|||||||
package mail_test
|
package plugin_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("mail_test.js")
|
r, err := gojs.RunFile("plugin_test.js")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
} else if r != true {
|
} else if r != true {
|
||||||
|
|||||||
135
smtp.go
135
smtp.go
@ -1,135 +0,0 @@
|
|||||||
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