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 }