2018-02-10 22:36:21 +00:00
|
|
|
package mail
|
2017-11-27 02:52:14 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"fmt"
|
|
|
|
"html/template"
|
2018-02-10 22:36:21 +00:00
|
|
|
"net/mail"
|
2017-11-27 02:52:14 +00:00
|
|
|
"net/url"
|
|
|
|
"strings"
|
|
|
|
|
2018-02-12 00:24:43 +00:00
|
|
|
"github.com/kirsle/blog/internal/log"
|
|
|
|
"github.com/kirsle/blog/internal/markdown"
|
2018-05-12 03:15:16 +00:00
|
|
|
"github.com/kirsle/blog/internal/render"
|
2018-02-12 00:24:43 +00:00
|
|
|
"github.com/kirsle/blog/models/comments"
|
|
|
|
"github.com/kirsle/blog/models/settings"
|
2017-11-27 03:05:31 +00:00
|
|
|
"github.com/microcosm-cc/bluemonday"
|
2017-11-27 02:52:14 +00:00
|
|
|
gomail "gopkg.in/gomail.v2"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Email configuration.
|
|
|
|
type Email struct {
|
|
|
|
To string
|
2017-12-23 02:34:58 +00:00
|
|
|
ReplyTo string
|
2017-11-27 02:52:14 +00:00
|
|
|
Admin bool /* admin view of the email */
|
|
|
|
Subject string
|
|
|
|
UnsubscribeURL string
|
|
|
|
Data map[string]interface{}
|
|
|
|
|
|
|
|
Template string
|
|
|
|
}
|
|
|
|
|
|
|
|
// SendEmail sends an email.
|
2018-02-10 22:36:21 +00:00
|
|
|
func SendEmail(email Email) {
|
2017-11-27 02:52:14 +00:00
|
|
|
s, _ := settings.Load()
|
2018-05-12 03:15:16 +00:00
|
|
|
|
|
|
|
// Suppress sending any mail when no mail settings are configured, but go
|
|
|
|
// through the motions -- great for local dev.
|
|
|
|
var doNotMail bool
|
2017-11-27 02:52:14 +00:00
|
|
|
if !s.Mail.Enabled || s.Mail.Host == "" || s.Mail.Port == 0 || s.Mail.Sender == "" {
|
|
|
|
log.Info("Suppressing email: not completely configured")
|
2018-05-12 03:15:16 +00:00
|
|
|
doNotMail = true
|
2017-11-27 02:52:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Resolve the template.
|
2018-02-10 18:08:45 +00:00
|
|
|
tmpl, err := render.ResolvePath(email.Template)
|
2017-11-27 02:52:14 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Error("SendEmail: %s", err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Render the template to HTML.
|
|
|
|
var html bytes.Buffer
|
|
|
|
t := template.New(tmpl.Basename)
|
|
|
|
t, err = template.ParseFiles(tmpl.Absolute)
|
|
|
|
if err != nil {
|
|
|
|
log.Error("SendEmail: template parsing error: %s", err.Error())
|
|
|
|
}
|
|
|
|
err = t.ExecuteTemplate(&html, tmpl.Basename, email)
|
|
|
|
if err != nil {
|
|
|
|
log.Error("SendEmail: template execution error: %s", err.Error())
|
|
|
|
}
|
|
|
|
|
2017-11-27 03:05:31 +00:00
|
|
|
// Condense the body down to plain text, lazily. Who even has a plain text
|
|
|
|
// email client anymore?
|
|
|
|
rawLines := strings.Split(
|
|
|
|
bluemonday.StrictPolicy().Sanitize(html.String()),
|
|
|
|
"\n",
|
|
|
|
)
|
|
|
|
var lines []string
|
|
|
|
for _, line := range rawLines {
|
|
|
|
line = strings.TrimSpace(line)
|
|
|
|
if len(line) == 0 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
lines = append(lines, line)
|
|
|
|
}
|
|
|
|
plaintext := strings.Join(lines, "\n\n")
|
|
|
|
|
2018-05-12 03:15:16 +00:00
|
|
|
// If we're not actually going to send the mail, this is a good place to stop.
|
|
|
|
if doNotMail {
|
|
|
|
log.Info("Not going to send an email.")
|
|
|
|
log.Debug("The message was going to be:\n%s", plaintext)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2017-11-27 02:52:14 +00:00
|
|
|
m := gomail.NewMessage()
|
2017-11-27 03:05:31 +00:00
|
|
|
m.SetHeader("From", fmt.Sprintf("%s <%s>", s.Site.Title, s.Mail.Sender))
|
2017-11-27 02:52:14 +00:00
|
|
|
m.SetHeader("To", email.To)
|
2017-12-23 02:34:58 +00:00
|
|
|
if email.ReplyTo != "" {
|
|
|
|
m.SetHeader("Reply-To", email.ReplyTo)
|
|
|
|
}
|
2017-11-27 02:52:14 +00:00
|
|
|
m.SetHeader("Subject", email.Subject)
|
2017-11-27 03:05:31 +00:00
|
|
|
m.SetBody("text/plain", plaintext)
|
|
|
|
m.AddAlternative("text/html", html.String())
|
2017-11-27 02:52:14 +00:00
|
|
|
|
|
|
|
d := gomail.NewDialer(s.Mail.Host, s.Mail.Port, s.Mail.Username, s.Mail.Password)
|
2017-12-23 02:34:58 +00:00
|
|
|
|
|
|
|
log.Info("SendEmail: %s (%s) to %s", email.Subject, email.Template, email.To)
|
2017-11-27 02:52:14 +00:00
|
|
|
if err := d.DialAndSend(m); err != nil {
|
|
|
|
log.Error("SendEmail: %s", err.Error())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// NotifyComment sends notification emails about comments.
|
2018-02-10 22:36:21 +00:00
|
|
|
func NotifyComment(c *comments.Comment) {
|
2017-11-27 02:52:14 +00:00
|
|
|
s, _ := settings.Load()
|
|
|
|
if s.Site.URL == "" {
|
|
|
|
log.Error("Can't send comment notification because the site URL is not configured")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Prepare the email payload.
|
|
|
|
email := Email{
|
|
|
|
Template: ".email/comment.gohtml",
|
|
|
|
Subject: "Comment Added: " + c.Subject,
|
|
|
|
Data: map[string]interface{}{
|
|
|
|
"Name": c.Name,
|
|
|
|
"Subject": c.Subject,
|
2018-02-10 03:01:56 +00:00
|
|
|
"Body": template.HTML(markdown.RenderMarkdown(c.Body)),
|
2017-11-27 02:52:14 +00:00
|
|
|
"URL": strings.Trim(s.Site.URL, "/") + c.OriginURL,
|
|
|
|
"QuickDelete": fmt.Sprintf("%s/comments/quick-delete?t=%s&d=%s",
|
|
|
|
strings.Trim(s.Site.URL, "/"),
|
|
|
|
url.QueryEscape(c.ThreadID),
|
|
|
|
url.QueryEscape(c.DeleteToken),
|
|
|
|
),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
// Email the site admins.
|
|
|
|
config, _ := settings.Load()
|
|
|
|
if config.Site.AdminEmail != "" {
|
|
|
|
email.To = config.Site.AdminEmail
|
|
|
|
email.Admin = true
|
|
|
|
log.Info("Mail site admin '%s' about comment notification on '%s'", email.To, c.ThreadID)
|
2018-02-10 22:36:21 +00:00
|
|
|
SendEmail(email)
|
2017-11-27 02:52:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Email the subscribers.
|
|
|
|
email.Admin = false
|
|
|
|
m := comments.LoadMailingList()
|
|
|
|
for _, to := range m.List(c.ThreadID) {
|
|
|
|
if to == c.Email {
|
|
|
|
continue // don't email yourself
|
|
|
|
}
|
|
|
|
email.To = to
|
|
|
|
email.UnsubscribeURL = fmt.Sprintf("%s/comments/subscription?t=%s&e=%s",
|
|
|
|
strings.Trim(s.Site.URL, "/"),
|
|
|
|
url.QueryEscape(c.ThreadID),
|
|
|
|
url.QueryEscape(to),
|
|
|
|
)
|
|
|
|
log.Info("Mail subscriber '%s' about comment notification on '%s'", email.To, c.ThreadID)
|
2018-02-10 22:36:21 +00:00
|
|
|
SendEmail(email)
|
2017-11-27 02:52:14 +00:00
|
|
|
}
|
|
|
|
}
|
2018-02-10 22:36:21 +00:00
|
|
|
|
|
|
|
// ParseAddress parses an email address.
|
|
|
|
func ParseAddress(addr string) (*mail.Address, error) {
|
|
|
|
return mail.ParseAddress(addr)
|
|
|
|
}
|