mail/plugin.go
2025-05-01 20:40:52 +08:00

232 lines
5.7 KiB
Go

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
}
}
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,
}
}
// 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
}