send multipart email with both html and plaintext by default, closes #22
This commit is contained in:
@@ -17,7 +17,6 @@ const (
|
|||||||
commandHelp = "help"
|
commandHelp = "help"
|
||||||
commandStop = "stop"
|
commandStop = "stop"
|
||||||
commandSend = "send"
|
commandSend = "send"
|
||||||
commandSendHTML = "send:html"
|
|
||||||
commandDKIM = "dkim"
|
commandDKIM = "dkim"
|
||||||
commandCatchAll = botOptionCatchAll
|
commandCatchAll = botOptionCatchAll
|
||||||
commandUsers = botOptionUsers
|
commandUsers = botOptionUsers
|
||||||
@@ -68,11 +67,6 @@ func (b *Bot) initCommands() commandList {
|
|||||||
description: "Send email",
|
description: "Send email",
|
||||||
allowed: b.allowSend,
|
allowed: b.allowSend,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
key: commandSendHTML,
|
|
||||||
description: "Send email, converting markdown to HTML",
|
|
||||||
allowed: b.allowSend,
|
|
||||||
},
|
|
||||||
{allowed: b.allowOwner}, // delimiter
|
{allowed: b.allowOwner}, // delimiter
|
||||||
// options commands
|
// options commands
|
||||||
{
|
{
|
||||||
@@ -273,9 +267,7 @@ func (b *Bot) handleCommand(ctx context.Context, evt *event.Event, commandSlice
|
|||||||
case commandStop:
|
case commandStop:
|
||||||
b.runStop(ctx)
|
b.runStop(ctx)
|
||||||
case commandSend:
|
case commandSend:
|
||||||
b.runSend(ctx, false)
|
b.runSend(ctx)
|
||||||
case commandSendHTML:
|
|
||||||
b.runSend(ctx, true)
|
|
||||||
case commandDKIM:
|
case commandDKIM:
|
||||||
b.runDKIM(ctx, commandSlice)
|
b.runDKIM(ctx, commandSlice)
|
||||||
case commandUsers:
|
case commandUsers:
|
||||||
@@ -386,30 +378,27 @@ func (b *Bot) sendHelp(ctx context.Context) {
|
|||||||
b.SendNotice(ctx, evt.RoomID, msg.String())
|
b.SendNotice(ctx, evt.RoomID, msg.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bot) runSend(ctx context.Context, html bool) {
|
func (b *Bot) runSend(ctx context.Context) {
|
||||||
evt := eventFromContext(ctx)
|
evt := eventFromContext(ctx)
|
||||||
if !b.allowSend(evt.Sender, evt.RoomID) {
|
if !b.allowSend(evt.Sender, evt.RoomID) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
subcommand := "send"
|
|
||||||
if html {
|
|
||||||
subcommand = "send:html"
|
|
||||||
}
|
|
||||||
commandSlice := b.parseCommand(evt.Content.AsMessage().Body, false)
|
commandSlice := b.parseCommand(evt.Content.AsMessage().Body, false)
|
||||||
to, subject, body, err := utils.ParseSend(commandSlice)
|
to, subject, body, err := utils.ParseSend(commandSlice)
|
||||||
if err == utils.ErrInvalidArgs {
|
if err == utils.ErrInvalidArgs {
|
||||||
b.SendNotice(ctx, evt.RoomID, fmt.Sprintf(
|
b.SendNotice(ctx, evt.RoomID, fmt.Sprintf(
|
||||||
"Usage:\n"+
|
"Usage:\n"+
|
||||||
"```\n"+
|
"```\n"+
|
||||||
"%s %s someone@example.com\n"+
|
"%s send someone@example.com\n"+
|
||||||
"Subject goes here on a line of its own\n"+
|
"Subject goes here on a line of its own\n"+
|
||||||
"Email content goes here\n"+
|
"Email content goes here\n"+
|
||||||
"on as many lines\n"+
|
"on as many lines\n"+
|
||||||
"as you want.\n"+
|
"as you want.\n"+
|
||||||
"```",
|
"```",
|
||||||
b.prefix, subcommand))
|
b.prefix))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
htmlBody := format.RenderMarkdown(body, true, true).FormattedBody
|
||||||
|
|
||||||
cfg, err := b.getRoomSettings(evt.RoomID)
|
cfg, err := b.getRoomSettings(evt.RoomID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -432,11 +421,6 @@ func (b *Bot) runSend(ctx context.Context, html bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var htmlBody string
|
|
||||||
if html {
|
|
||||||
htmlBody = format.RenderMarkdown(body, true, true).FormattedBody
|
|
||||||
}
|
|
||||||
|
|
||||||
b.lock(evt.RoomID.String())
|
b.lock(evt.RoomID.String())
|
||||||
defer b.unlock(evt.RoomID.String())
|
defer b.unlock(evt.RoomID.String())
|
||||||
|
|
||||||
@@ -445,7 +429,7 @@ func (b *Bot) runSend(ctx context.Context, html bool) {
|
|||||||
ID := utils.MessageID(evt.ID, domain)
|
ID := utils.MessageID(evt.ID, domain)
|
||||||
for _, to := range tos {
|
for _, to := range tos {
|
||||||
email := utils.NewEmail(ID, "", " "+ID, subject, from, to, body, htmlBody, nil)
|
email := utils.NewEmail(ID, "", " "+ID, subject, from, to, body, htmlBody, nil)
|
||||||
data := email.Compose(html, b.getBotSettings().DKIMPrivateKey())
|
data := email.Compose(b.getBotSettings().DKIMPrivateKey())
|
||||||
queued, err := b.Sendmail(evt.ID, from, to, data)
|
queued, err := b.Sendmail(evt.ID, from, to, data)
|
||||||
if queued {
|
if queued {
|
||||||
b.log.Error("cannot send email: %v", err)
|
b.log.Error("cannot send email: %v", err)
|
||||||
|
|||||||
@@ -179,7 +179,7 @@ func (b *Bot) SendEmailReply(ctx context.Context) {
|
|||||||
meta.References = meta.References + " " + meta.MessageID
|
meta.References = meta.References + " " + meta.MessageID
|
||||||
b.log.Debug("send email reply: %+v", meta)
|
b.log.Debug("send email reply: %+v", meta)
|
||||||
email := utils.NewEmail(meta.MessageID, meta.InReplyTo, meta.References, meta.Subject, meta.From, meta.To, body, htmlBody, nil)
|
email := utils.NewEmail(meta.MessageID, meta.InReplyTo, meta.References, meta.Subject, meta.From, meta.To, body, htmlBody, nil)
|
||||||
data := email.Compose(false, b.getBotSettings().DKIMPrivateKey())
|
data := email.Compose(b.getBotSettings().DKIMPrivateKey())
|
||||||
|
|
||||||
queued, err := b.Sendmail(evt.ID, meta.From, meta.To, data)
|
queued, err := b.Sendmail(evt.ID, meta.From, meta.To, data)
|
||||||
if queued {
|
if queued {
|
||||||
|
|||||||
@@ -156,7 +156,7 @@ func (s *outgoingSession) Data(r io.Reader) error {
|
|||||||
eml.HTML,
|
eml.HTML,
|
||||||
files)
|
files)
|
||||||
|
|
||||||
return s.sendmail(email.From, email.To, email.Compose(false, s.privkey))
|
return s.sendmail(email.From, email.To, email.Compose(s.privkey))
|
||||||
}
|
}
|
||||||
func (s *outgoingSession) Reset() {}
|
func (s *outgoingSession) Reset() {}
|
||||||
func (s *outgoingSession) Logout() error { return nil }
|
func (s *outgoingSession) Logout() error { return nil }
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/emersion/go-msgauth/dkim"
|
"github.com/emersion/go-msgauth/dkim"
|
||||||
|
"gitlab.com/etke.cc/go/secgen"
|
||||||
"maunium.net/go/mautrix/event"
|
"maunium.net/go/mautrix/event"
|
||||||
"maunium.net/go/mautrix/format"
|
"maunium.net/go/mautrix/format"
|
||||||
"maunium.net/go/mautrix/id"
|
"maunium.net/go/mautrix/id"
|
||||||
@@ -141,19 +142,18 @@ func (e *Email) Content(threadID id.EventID, options *ContentOptions) *event.Con
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Compose converts the email object to a string (to be used for delivery via SMTP) and possibly DKIM-signs it
|
// Compose converts the email object to a string (to be used for delivery via SMTP) and possibly DKIM-signs it
|
||||||
func (e *Email) Compose(html bool, privkey string) string {
|
func (e *Email) Compose(privkey string) string {
|
||||||
var data strings.Builder
|
var data strings.Builder
|
||||||
|
|
||||||
domain := strings.SplitN(e.From, "@", 2)[1]
|
domain := strings.SplitN(e.From, "@", 2)[1]
|
||||||
|
boundaryName := "postmoogle" + secgen.Password(32)
|
||||||
|
boundary := "--" + boundaryName
|
||||||
|
|
||||||
data.WriteString("MIME-Version: 1.0")
|
data.WriteString("MIME-Version: 1.0")
|
||||||
data.WriteString("\r\n")
|
data.WriteString("\r\n")
|
||||||
|
|
||||||
if html {
|
data.WriteString("Content-Type: multipart/alternative; boundary=")
|
||||||
data.WriteString("Content-Type: text/html; charset=\"UTF-8\"")
|
data.WriteString(boundaryName)
|
||||||
} else {
|
|
||||||
data.WriteString("Content-Type: text/plain; charset=\"UTF-8\"")
|
|
||||||
}
|
|
||||||
data.WriteString("\r\n")
|
data.WriteString("\r\n")
|
||||||
|
|
||||||
data.WriteString("Content-Transfer-Encoding: 8BIT")
|
data.WriteString("Content-Transfer-Encoding: 8BIT")
|
||||||
@@ -190,14 +190,26 @@ func (e *Email) Compose(html bool, privkey string) string {
|
|||||||
data.WriteString("Subject: ")
|
data.WriteString("Subject: ")
|
||||||
data.WriteString(e.Subject)
|
data.WriteString(e.Subject)
|
||||||
data.WriteString("\r\n")
|
data.WriteString("\r\n")
|
||||||
|
|
||||||
data.WriteString("\r\n")
|
data.WriteString("\r\n")
|
||||||
|
|
||||||
if html {
|
data.WriteString(boundary)
|
||||||
|
data.WriteString("\r\n")
|
||||||
|
data.WriteString("Content-Type: text/html; charset=\"UTF-8\"")
|
||||||
|
data.WriteString("\r\n")
|
||||||
data.WriteString(e.HTML)
|
data.WriteString(e.HTML)
|
||||||
} else {
|
data.WriteString("\r\n")
|
||||||
|
data.WriteString("\r\n")
|
||||||
|
|
||||||
|
data.WriteString(boundary)
|
||||||
|
data.WriteString("\r\n")
|
||||||
|
data.WriteString("Content-Type: text/plain; charset=\"UTF-8\"")
|
||||||
|
data.WriteString("\r\n")
|
||||||
data.WriteString(e.Text)
|
data.WriteString(e.Text)
|
||||||
}
|
data.WriteString("\r\n")
|
||||||
|
data.WriteString("\r\n")
|
||||||
|
|
||||||
|
data.WriteString(boundary)
|
||||||
|
data.WriteString("--")
|
||||||
data.WriteString("\r\n")
|
data.WriteString("\r\n")
|
||||||
|
|
||||||
return e.sign(domain, privkey, data)
|
return e.sign(domain, privkey, data)
|
||||||
|
|||||||
Reference in New Issue
Block a user