support imap
This commit is contained in:
		
							parent
							
								
									182b2fd96f
								
							
						
					
					
						commit
						5d3b0c78d8
					
				
							
								
								
									
										42
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										42
									
								
								go.mod
									
									
									
									
									
								
							@ -1,34 +1,38 @@
 | 
			
		||||
module apigo.cc/gojs/mail
 | 
			
		||||
 | 
			
		||||
go 1.18
 | 
			
		||||
go 1.24
 | 
			
		||||
 | 
			
		||||
require (
 | 
			
		||||
	apigo.cc/gojs v0.0.12
 | 
			
		||||
	apigo.cc/gojs v0.0.25
 | 
			
		||||
	apigo.cc/gojs/console v0.0.2
 | 
			
		||||
	apigo.cc/gojs/redis v0.0.2
 | 
			
		||||
	github.com/ssgo/u v1.7.13
 | 
			
		||||
	github.com/emersion/go-imap/v2 v2.0.0-beta.5
 | 
			
		||||
	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 (
 | 
			
		||||
	github.com/dlclark/regexp2 v1.11.4 // indirect
 | 
			
		||||
	github.com/emersion/go-imap v1.2.1 // indirect
 | 
			
		||||
	github.com/fsnotify/fsnotify v1.8.0 // indirect
 | 
			
		||||
	github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a // indirect
 | 
			
		||||
	github.com/dlclark/regexp2 v1.11.5 // 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/gomodule/redigo v1.9.2 // indirect
 | 
			
		||||
	github.com/google/pprof v0.0.0-20230207041349-798e818bf904 // indirect
 | 
			
		||||
	github.com/jaytaylor/html2text v0.0.0-20230321000545-74c2419ad056 // indirect
 | 
			
		||||
	github.com/mattn/go-runewidth v0.0.9 // indirect
 | 
			
		||||
	github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f // indirect
 | 
			
		||||
	github.com/google/pprof v0.0.0-20250630185457-6e76a2b096b5 // indirect
 | 
			
		||||
	github.com/jhillyerd/enmime v1.3.0 // indirect
 | 
			
		||||
	github.com/mattn/go-runewidth v0.0.15 // indirect
 | 
			
		||||
	github.com/olekukonko/tablewriter v0.0.5 // indirect
 | 
			
		||||
	github.com/ssgo/config v1.7.9 // indirect
 | 
			
		||||
	github.com/ssgo/log v1.7.7 // indirect
 | 
			
		||||
	github.com/ssgo/redis v1.7.7 // indirect
 | 
			
		||||
	github.com/pkg/errors v0.9.1 // indirect
 | 
			
		||||
	github.com/rivo/uniseg v0.4.4 // indirect
 | 
			
		||||
	github.com/ssgo/log v1.7.9 // 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
 | 
			
		||||
	golang.org/x/net v0.32.0 // indirect
 | 
			
		||||
	golang.org/x/sys v0.28.0 // indirect
 | 
			
		||||
	golang.org/x/text v0.21.0 // indirect
 | 
			
		||||
	golang.org/x/net v0.42.0 // indirect
 | 
			
		||||
	golang.org/x/sys v0.34.0 // indirect
 | 
			
		||||
	golang.org/x/text v0.27.0 // 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
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										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 (
 | 
			
		||||
	"crypto/tls"
 | 
			
		||||
	"encoding/base64"
 | 
			
		||||
	"io"
 | 
			
		||||
	"runtime"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"apigo.cc/gojs"
 | 
			
		||||
	"apigo.cc/gojs/goja"
 | 
			
		||||
	"github.com/jaytaylor/html2text"
 | 
			
		||||
	"github.com/ssgo/config"
 | 
			
		||||
	"github.com/ssgo/log"
 | 
			
		||||
	"github.com/ssgo/u"
 | 
			
		||||
	"gopkg.in/gomail.v2"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
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 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() {
 | 
			
		||||
	tsCode := gojs.MakeTSCode(defaultObject)
 | 
			
		||||
	gojs.Register("apigo.cc/gojs/"+pluginName, gojs.Module{
 | 
			
		||||
		ObjectMaker: func(vm *goja.Runtime) gojs.Map {
 | 
			
		||||
			configs := make(map[string]*Config)
 | 
			
		||||
			config.LoadConfig(pluginName, &configs)
 | 
			
		||||
			for name, object := range configs {
 | 
			
		||||
				objects[name] = defaultObject.New(*object)
 | 
			
		||||
			config.LoadConfig(pluginName, conf)
 | 
			
		||||
			for name, object := range conf.Mailbox {
 | 
			
		||||
				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)
 | 
			
		||||
		},
 | 
			
		||||
		TsCode: tsCode,
 | 
			
		||||
		Desc:   "mail api by github.com/ssgo/mail",
 | 
			
		||||
		Desc:   "mail api",
 | 
			
		||||
		SetSSKey: func(key, iv []byte) {
 | 
			
		||||
			confAes = u.NewAes(key, iv)
 | 
			
		||||
		},
 | 
			
		||||
@ -42,18 +70,18 @@ func init() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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 {
 | 
			
		||||
		return o
 | 
			
		||||
		return o, nil
 | 
			
		||||
	} 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)
 | 
			
		||||
	if len(a) == 2 {
 | 
			
		||||
		if conf.SmtpHost == "" {
 | 
			
		||||
@ -69,8 +97,11 @@ func (obj *DefaultObject) New(conf Config) *Object {
 | 
			
		||||
			conf.ImapPort = 993
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if conf.SenderMail == "" {
 | 
			
		||||
		conf.SenderMail = conf.Username
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return &Object{
 | 
			
		||||
	return &Mailbox{
 | 
			
		||||
		smtpHost:   conf.SmtpHost,
 | 
			
		||||
		smtpPort:   conf.SmtpPort,
 | 
			
		||||
		imapHost:   conf.ImapHost,
 | 
			
		||||
@ -78,154 +109,6 @@ func (obj *DefaultObject) New(conf Config) *Object {
 | 
			
		||||
		username:   conf.Username,
 | 
			
		||||
		password:   confAes.DecryptUrlBase64ToString(conf.Password),
 | 
			
		||||
		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 (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"apigo.cc/gojs"
 | 
			
		||||
	_ "apigo.cc/gojs/console"
 | 
			
		||||
	"github.com/ssgo/u"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
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()
 | 
			
		||||
	r, err := gojs.RunFile("plugin_test.js")
 | 
			
		||||
	r, err := gojs.RunFile("mail_test.js")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	} 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