wid-notifier/mail.go

108 lines
2.9 KiB
Go

// Copyright (c) 2023 Julian Müller (ChaoticByte)
package main
import (
"encoding/base64"
"fmt"
"net/mail"
"net/smtp"
"slices"
"strings"
)
const MAIL_LINE_SEP = "\r\n"
type MailContent struct {
Subject string
Body string
}
func (c MailContent) serializeValidMail(from string, to string) []byte {
// We'll send base64 encoded Subject & Body, because we Dschörmäns have umlauts
// and I'm too lazy to encode ä into =E4 and so on
subjectEncoded := base64.StdEncoding.EncodeToString([]byte(c.Subject))
bodyEncoded := base64.StdEncoding.EncodeToString(
[]byte( // ensure that all lines end with CRLF
strings.ReplaceAll(
strings.ReplaceAll(c.Body, "\n", MAIL_LINE_SEP), "\r\r", "\r",
),
),
)
data := []byte(fmt.Sprintf(
"Content-Type: text/plain; charset=\"utf-8\"\r\nContent-Transfer-Encoding: base64\r\nFrom: %v%vTo: %v%vSubject: =?utf-8?b?%v?=%v%v%v",
from, MAIL_LINE_SEP,
to, MAIL_LINE_SEP,
subjectEncoded, MAIL_LINE_SEP,
MAIL_LINE_SEP,
bodyEncoded))
// done, I guess
return data
}
type Recipient struct {
Address string `json:"address"`
// Must be a configured filter id
Filters []Filter `json:"include"`
}
type SmtpSettings struct {
From string `json:"from"`
ServerHost string `json:"host"`
ServerPort int `json:"port"`
User string `json:"user"`
Password string `json:"password"`
}
func (r Recipient) filterAndSendNotices(notices []WidNotice, template MailTemplate, auth smtp.Auth, smtpConfig SmtpSettings, cache *map[string][]byte) error {
filteredNotices := []WidNotice{}
for _, f := range r.Filters {
for _, n := range f.filter(notices) {
if !noticeSliceContains(filteredNotices, n) {
filteredNotices = append(filteredNotices, n)
}
}
}
slices.Reverse(filteredNotices)
logger.debug(fmt.Sprintf("Including %v of %v notices for recipient %v", len(filteredNotices), len(notices), r.Address))
logger.debug("Generating and sending mails to " + r.Address + " ...")
cacheHits := 0
cacheMisses := 0
for _, n := range filteredNotices {
var data []byte
cacheResult := (*cache)[n.Uuid]
if len(cacheResult) > 0 {
cacheHits++
data = cacheResult
} else {
cacheMisses++
mailContent, err := template.generate(n)
if err != nil {
logger.error("Could not create mail from template")
logger.error(err)
}
// serialize & send mail
data = mailContent.serializeValidMail(smtpConfig.From, r.Address)
// add to cache
(*cache)[n.Uuid] = data
}
err := smtp.SendMail(
fmt.Sprintf("%v:%v", smtpConfig.ServerHost, smtpConfig.ServerPort),
auth,
smtpConfig.From,
[]string{r.Address},
data,
)
if err != nil {
return err
}
}
logger.debug(fmt.Sprintf("%v mail cache hits, %v misses", cacheHits, cacheMisses))
logger.debug("Successfully sent all mails to " + r.Address)
return nil
}
func mailAddressIsValid(address string) bool {
_, err := mail.ParseAddress(address);
return err == nil
}