2025-05-01 16:32:41 +08:00
|
|
|
package plugin
|
|
|
|
|
|
|
|
import (
|
|
|
|
"crypto/tls"
|
|
|
|
"encoding/base64"
|
|
|
|
"io"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"apigo.cc/gojs"
|
|
|
|
"apigo.cc/gojs/goja"
|
|
|
|
"github.com/jaytaylor/html2text"
|
|
|
|
"github.com/ssgo/config"
|
|
|
|
"github.com/ssgo/u"
|
|
|
|
"gopkg.in/gomail.v2"
|
|
|
|
)
|
|
|
|
|
|
|
|
const pluginName = "mail"
|
|
|
|
|
|
|
|
var defaultObjectName = "default"
|
|
|
|
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)
|
|
|
|
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
defaultObject.Object = defaultObject.GetInstance("default")
|
|
|
|
return gojs.ToMap(defaultObject)
|
|
|
|
},
|
|
|
|
TsCode: tsCode,
|
|
|
|
Desc: "mail api by github.com/ssgo/mail",
|
|
|
|
SetSSKey: func(key, iv []byte) {
|
|
|
|
confAes = u.NewAes(key, iv)
|
|
|
|
},
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
type DefaultObject struct {
|
|
|
|
*Object
|
|
|
|
}
|
|
|
|
|
|
|
|
func (obj *DefaultObject) GetInstance(name string) *Object {
|
|
|
|
if o, ok := objects[name]; ok {
|
|
|
|
return o
|
|
|
|
} else {
|
|
|
|
return obj.Object
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (obj *DefaultObject) New(conf Config) *Object {
|
|
|
|
a := strings.SplitN(conf.Username, "@", 2)
|
|
|
|
if len(a) == 2 {
|
|
|
|
if conf.SmtpHost == "" {
|
|
|
|
conf.SmtpHost = "smtp." + a[1]
|
|
|
|
}
|
|
|
|
if conf.SmtpPort == 0 {
|
|
|
|
conf.SmtpPort = 465
|
|
|
|
}
|
|
|
|
if conf.ImapHost == "" {
|
|
|
|
conf.ImapHost = "imap." + a[1]
|
|
|
|
}
|
|
|
|
if conf.ImapPort == 0 {
|
|
|
|
conf.ImapPort = 993
|
|
|
|
}
|
|
|
|
}
|
2025-05-01 20:40:52 +08:00
|
|
|
|
2025-05-01 16:32:41 +08:00
|
|
|
return &Object{
|
|
|
|
smtpHost: conf.SmtpHost,
|
|
|
|
smtpPort: conf.SmtpPort,
|
|
|
|
imapHost: conf.ImapHost,
|
|
|
|
imapPort: conf.ImapPort,
|
|
|
|
username: conf.Username,
|
|
|
|
password: confAes.DecryptUrlBase64ToString(conf.Password),
|
|
|
|
senderName: conf.SenderName,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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,
|
|
|
|
}
|
|
|
|
}
|
2025-05-01 16:39:05 +08:00
|
|
|
// conn.Auth = smtp.PlainAuth("", obj.username, obj.password, obj.smtpHost)
|
2025-05-01 16:32:41 +08:00
|
|
|
|
|
|
|
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
|
|
|
|
}
|