2020-02-14 06:37:23 +00:00
|
|
|
package mail
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"fmt"
|
|
|
|
"html/template"
|
|
|
|
"net/mail"
|
|
|
|
"net/url"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"git.kirsle.net/apps/gophertype/pkg/console"
|
|
|
|
"git.kirsle.net/apps/gophertype/pkg/markdown"
|
|
|
|
"git.kirsle.net/apps/gophertype/pkg/models"
|
|
|
|
"git.kirsle.net/apps/gophertype/pkg/responses"
|
|
|
|
"git.kirsle.net/apps/gophertype/pkg/settings"
|
|
|
|
"github.com/microcosm-cc/bluemonday"
|
|
|
|
gomail "gopkg.in/gomail.v2"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Email configuration.
|
|
|
|
type Email struct {
|
|
|
|
To string
|
|
|
|
ReplyTo string
|
|
|
|
Admin bool /* admin view of the email */
|
|
|
|
Subject string
|
|
|
|
UnsubscribeURL string
|
|
|
|
Data map[string]interface{}
|
|
|
|
|
|
|
|
Template string
|
|
|
|
}
|
|
|
|
|
|
|
|
// SendEmail sends an email.
|
|
|
|
func SendEmail(email Email) {
|
|
|
|
s := settings.Current
|
|
|
|
|
|
|
|
// Suppress sending any mail when no mail settings are configured, but go
|
|
|
|
// through the motions -- great for local dev.
|
|
|
|
var doNotMail bool
|
|
|
|
if !s.MailEnabled || s.MailHost == "" || s.MailPort == 0 || s.MailSender == "" {
|
|
|
|
console.Info("Suppressing email: not completely configured")
|
|
|
|
doNotMail = true
|
|
|
|
}
|
|
|
|
|
|
|
|
// Resolve the template.
|
|
|
|
tmpl, err := responses.GetFile(email.Template)
|
|
|
|
if err != nil {
|
|
|
|
console.Error("SendEmail: %s", err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Render the template to HTML.
|
|
|
|
var html bytes.Buffer
|
|
|
|
t := template.New(email.Template)
|
|
|
|
t, err = t.Parse(string(tmpl))
|
|
|
|
if err != nil {
|
|
|
|
console.Error("SendEmail: template parsing error: %s", err.Error())
|
|
|
|
}
|
|
|
|
|
|
|
|
// Execute the template.
|
|
|
|
err = t.ExecuteTemplate(&html, email.Template, email)
|
|
|
|
if err != nil {
|
|
|
|
console.Error("SendEmail: template execution error: %s", err.Error())
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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")
|
|
|
|
|
|
|
|
// If we're not actually going to send the mail, this is a good place to stop.
|
|
|
|
if doNotMail {
|
|
|
|
console.Info("Not going to send an email.")
|
|
|
|
console.Debug("The message was going to be:\n%s", plaintext)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
m := gomail.NewMessage()
|
|
|
|
m.SetHeader("From", fmt.Sprintf("%s <%s>", s.Title, s.MailSender))
|
|
|
|
m.SetHeader("To", email.To)
|
|
|
|
if email.ReplyTo != "" {
|
|
|
|
m.SetHeader("Reply-To", email.ReplyTo)
|
|
|
|
}
|
|
|
|
m.SetHeader("Subject", email.Subject)
|
|
|
|
m.SetBody("text/plain", plaintext)
|
|
|
|
m.AddAlternative("text/html", html.String())
|
|
|
|
|
|
|
|
d := gomail.NewDialer(s.MailHost, s.MailPort, s.MailUsername, s.MailPassword)
|
|
|
|
|
|
|
|
console.Info("SendEmail: %s (%s) to %s", email.Subject, email.Template, email.To)
|
|
|
|
if err := d.DialAndSend(m); err != nil {
|
|
|
|
console.Error("SendEmail: %s", err.Error())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// NotifyComment sends notification emails about comments.
|
|
|
|
func NotifyComment(subject string, originURL string, c models.Comment) {
|
|
|
|
s := settings.Current
|
|
|
|
if s.BaseURL == "" {
|
|
|
|
console.Error("Can't send comment notification because the site URL is not configured")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Prepare the email payload.
|
|
|
|
email := Email{
|
|
|
|
Template: "_builtin/email/comment.gohtml",
|
|
|
|
Subject: "Comment Added: " + subject,
|
|
|
|
Data: map[string]interface{}{
|
|
|
|
"Name": c.Name,
|
|
|
|
"Subject": subject,
|
|
|
|
"Body": template.HTML(markdown.RenderMarkdown(c.Body)),
|
|
|
|
"URL": strings.Trim(s.BaseURL, "/") + originURL,
|
|
|
|
"QuickDelete": fmt.Sprintf("%s/comments/quick-delete?d=%s&next=%s",
|
|
|
|
strings.Trim(s.BaseURL, "/"),
|
|
|
|
url.QueryEscape(c.DeleteToken),
|
|
|
|
url.QueryEscape(strings.Trim(s.BaseURL, "/")+originURL),
|
|
|
|
),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
// Email the site admins.
|
|
|
|
if adminEmails, err := models.ListAdminEmails(); err == nil {
|
|
|
|
email.To = strings.Join(adminEmails, ", ")
|
|
|
|
email.Admin = true
|
|
|
|
console.Info("Mail site admin '%s' about comment notification on '%s'", email.To, c.Thread)
|
|
|
|
SendEmail(email)
|
|
|
|
}
|
|
|
|
|
|
|
|
// // Email the subscribers.
|
2020-02-16 03:43:08 +00:00
|
|
|
email.Admin = false
|
|
|
|
if subscribers, err := models.Comments.GetSubscribers(c.Thread); err == nil {
|
|
|
|
email.Subject = "A new comment has been added to: " + subject
|
|
|
|
|
|
|
|
for _, to := range subscribers {
|
|
|
|
// Don't email to the writer of the comment.
|
|
|
|
if to == c.Email {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
email.To = to
|
|
|
|
email.UnsubscribeURL = fmt.Sprintf("%s/comments/subscription?t=%s&e=%s",
|
|
|
|
strings.Trim(s.BaseURL, "/"),
|
|
|
|
url.QueryEscape(c.Thread),
|
|
|
|
url.QueryEscape(to),
|
|
|
|
)
|
|
|
|
console.Info("Mail subscriber '%s' about comment notification on '%s'", email.To, c.Thread)
|
|
|
|
SendEmail(email)
|
|
|
|
}
|
|
|
|
}
|
2020-02-14 06:37:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// ParseAddress parses an email address.
|
|
|
|
func ParseAddress(addr string) (*mail.Address, error) {
|
|
|
|
return mail.ParseAddress(addr)
|
|
|
|
}
|