add missing References email header, fix Message-Id composing, fix email reply bugs

This commit is contained in:
Aine
2022-11-13 15:33:19 +02:00
parent 0c01987c93
commit 29cd6c4dcb
5 changed files with 108 additions and 72 deletions

View File

@@ -364,15 +364,15 @@ func (b *Bot) runSend(ctx context.Context) {
defer b.unlock(evt.RoomID) defer b.unlock(evt.RoomID)
from := mailbox + "@" + b.domains[0] from := mailbox + "@" + b.domains[0]
ID := fmt.Sprintf("<%s@%s>", evt.ID, b.domains[0]) ID := utils.MessageID(evt.ID, b.domains[0])
for _, to := range tos { for _, to := range tos {
email := utils.NewEmail(ID, "", subject, from, to, body, "", nil) email := utils.NewEmail(ID, "", " "+ID, subject, from, to, body, "", nil)
data := email.Compose(b.getBotSettings().DKIMPrivateKey()) data := email.Compose(b.getBotSettings().DKIMPrivateKey())
err = b.sendmail(from, to, data) err = b.sendmail(from, to, data)
if err != nil { if err != nil {
b.Error(ctx, evt.RoomID, "cannot send email to %s: %v", to, err) b.Error(ctx, evt.RoomID, "cannot send email to %s: %v", to, err)
} else { } else {
b.forgeSentMetadata(ctx, email, &cfg) b.saveSentMetadata(ctx, email, &cfg)
} }
} }
if len(tos) > 1 { if len(tos) > 1 {
@@ -380,9 +380,9 @@ func (b *Bot) runSend(ctx context.Context) {
} }
} }
// forgeSentMetadata used to save metadata from !pm sent event to a separate notice message // saveSentMetadata used to save metadata from !pm sent event to a separate notice message
// because that metadata is needed to determine email thread relations // because that metadata is needed to determine email thread relations
func (b *Bot) forgeSentMetadata(ctx context.Context, email *utils.Email, cfg *roomSettings) { func (b *Bot) saveSentMetadata(ctx context.Context, email *utils.Email, cfg *roomSettings) {
evt := eventFromContext(ctx) evt := eventFromContext(ctx)
threadID := utils.EventParent(evt.ID, evt.Content.AsMessage()) threadID := utils.EventParent(evt.ID, evt.Content.AsMessage())
content := email.Content(threadID, cfg.ContentOptions()) content := email.Content(threadID, cfg.ContentOptions())
@@ -402,7 +402,7 @@ func (b *Bot) forgeSentMetadata(ctx context.Context, email *utils.Email, cfg *ro
return return
} }
if threadID != "" { if threadID != "" {
b.setThreadID(evt.RoomID, fmt.Sprintf("<%s@%s>", msgID, b.domains[0]), threadID) b.setThreadID(evt.RoomID, utils.MessageID(msgID, b.domains[0]), threadID)
} }
b.setLastEventID(evt.RoomID, threadID, msgID) b.setLastEventID(evt.RoomID, threadID, msgID)
} }

View File

@@ -19,11 +19,12 @@ const (
// event keys // event keys
const ( const (
eventMessageIDkey = "cc.etke.postmoogle.messageID" eventMessageIDkey = "cc.etke.postmoogle.messageID"
eventInReplyToKey = "cc.etke.postmoogle.inReplyTo" eventReferencesKey = "cc.etke.postmoogle.references"
eventSubjectKey = "cc.etke.postmoogle.subject" eventInReplyToKey = "cc.etke.postmoogle.inReplyTo"
eventFromKey = "cc.etke.postmoogle.from" eventSubjectKey = "cc.etke.postmoogle.subject"
eventToKey = "cc.etke.postmoogle.to" eventFromKey = "cc.etke.postmoogle.from"
eventToKey = "cc.etke.postmoogle.to"
) )
// SetSendmail sets mail sending func to the bot // SetSendmail sets mail sending func to the bot
@@ -115,46 +116,60 @@ func (b *Bot) IncomingEmail(ctx context.Context, email *utils.Email) error {
return nil return nil
} }
func (b *Bot) getParentEmail(evt *event.Event) (string, string, string, string) { type parentEmail struct {
MessageID string
From string
To string
InReplyTo string
References string
Subject string
}
func (b *Bot) getParentEmail(evt *event.Event) parentEmail {
var parent parentEmail
content := evt.Content.AsMessage() content := evt.Content.AsMessage()
parentID := utils.EventParent(evt.ID, content) parentID := utils.EventParent(evt.ID, content)
if parentID == evt.ID { if parentID == evt.ID {
return "", "", "", "" return parent
} }
parentID = b.getLastEventID(evt.RoomID, parentID) parentID = b.getLastEventID(evt.RoomID, parentID)
parentEvt, err := b.lp.GetClient().GetEvent(evt.RoomID, parentID) parentEvt, err := b.lp.GetClient().GetEvent(evt.RoomID, parentID)
if err != nil { if err != nil {
b.log.Error("cannot get parent event: %v", err) b.log.Error("cannot get parent event: %v", err)
return "", "", "", "" return parent
} }
if parentEvt.Content.Parsed == nil { if parentEvt.Content.Parsed == nil {
perr := parentEvt.Content.ParseRaw(event.EventMessage) perr := parentEvt.Content.ParseRaw(event.EventMessage)
if perr != nil { if perr != nil {
b.log.Error("cannot parse event content: %v", perr) b.log.Error("cannot parse event content: %v", perr)
return "", "", "", "" return parent
} }
} }
from := utils.EventField[string](&parentEvt.Content, eventFromKey) parent.MessageID = utils.MessageID(parentID, b.domains[0])
to := utils.EventField[string](&parentEvt.Content, eventToKey) parent.From = utils.EventField[string](&parentEvt.Content, eventFromKey)
inReplyTo := utils.EventField[string](&parentEvt.Content, eventMessageIDkey) parent.To = utils.EventField[string](&parentEvt.Content, eventToKey)
if inReplyTo == "" { parent.InReplyTo = utils.EventField[string](&parentEvt.Content, eventMessageIDkey)
inReplyTo = parentID.String() parent.References = utils.EventField[string](&parentEvt.Content, eventReferencesKey)
if parent.InReplyTo == "" {
parent.InReplyTo = parent.MessageID
}
if parent.References == "" {
parent.References = " " + parent.MessageID
} }
subject := utils.EventField[string](&parentEvt.Content, eventSubjectKey) parent.Subject = utils.EventField[string](&parentEvt.Content, eventSubjectKey)
if subject != "" { if parent.Subject != "" {
subject = "Re: " + subject parent.Subject = "Re: " + parent.Subject
} else { } else {
subject = strings.SplitN(content.Body, "\n", 1)[0] parent.Subject = strings.SplitN(content.Body, "\n", 1)[0]
} }
return from, to, inReplyTo, subject return parent
} }
// SendEmailReply sends replies from matrix thread to email thread // SendEmailReply sends replies from matrix thread to email thread
func (b *Bot) SendEmailReply(ctx context.Context) { func (b *Bot) SendEmailReply(ctx context.Context) {
var inReplyTo string
evt := eventFromContext(ctx) evt := eventFromContext(ctx)
cfg, err := b.getRoomSettings(evt.RoomID) cfg, err := b.getRoomSettings(evt.RoomID)
if err != nil { if err != nil {
@@ -169,35 +184,37 @@ func (b *Bot) SendEmailReply(ctx context.Context) {
b.lock(evt.RoomID) b.lock(evt.RoomID)
defer b.unlock(evt.RoomID) defer b.unlock(evt.RoomID)
fromMailbox := mailbox + "@" + b.domains[0] fromMailbox := mailbox + "@" + b.domains[0]
from, to, inReplyTo, subject := b.getParentEmail(evt) meta := b.getParentEmail(evt)
// when email was sent from matrix and reply was sent from matrix again // when email was sent from matrix and reply was sent from matrix again
if fromMailbox != from { if fromMailbox != meta.From {
to = from meta.To = meta.From
} }
if to == "" { if meta.To == "" {
b.Error(ctx, evt.RoomID, "cannot find parent email and continue the thread. Please, start a new email thread") b.Error(ctx, evt.RoomID, "cannot find parent email and continue the thread. Please, start a new email thread")
return return
} }
content := evt.Content.AsMessage() content := evt.Content.AsMessage()
if subject == "" { if meta.Subject == "" {
subject = strings.SplitN(content.Body, "\n", 1)[0] meta.Subject = strings.SplitN(content.Body, "\n", 1)[0]
} }
body := content.Body body := content.Body
ID := evt.ID.String()[1:] + "@" + b.domains[0] ID := utils.MessageID(evt.ID, b.domains[0])
b.log.Debug("send email reply ID=%s from=%s to=%s inReplyTo=%s subject=%s body=%s", ID, from, to, inReplyTo, subject, body) meta.References = meta.References + " " + ID
data := utils. b.log.Debug("send email reply ID=%s meta=%+v", ID, meta)
NewEmail(ID, inReplyTo, subject, from, to, body, "", nil). email := utils.NewEmail(ID, meta.InReplyTo, meta.References, meta.Subject, meta.From, meta.To, body, "", nil)
Compose(b.getBotSettings().DKIMPrivateKey()) data := email.Compose(b.getBotSettings().DKIMPrivateKey())
err = b.sendmail(from, to, data) err = b.sendmail(meta.From, meta.To, data)
if err != nil { if err != nil {
b.Error(ctx, evt.RoomID, "cannot send email: %v", err) b.Error(ctx, evt.RoomID, "cannot send email: %v", err)
return return
} }
b.saveSentMetadata(ctx, email, &cfg)
} }
func (b *Bot) sendFiles(ctx context.Context, roomID id.RoomID, files []*utils.File, noThreads bool, parentID id.EventID) { func (b *Bot) sendFiles(ctx context.Context, roomID id.RoomID, files []*utils.File, noThreads bool, parentID id.EventID) {

View File

@@ -146,11 +146,12 @@ func (s roomSettings) ContentOptions() *utils.ContentOptions {
Subject: !s.NoSubject(), Subject: !s.NoSubject(),
Threads: !s.NoThreads(), Threads: !s.NoThreads(),
ToKey: eventToKey, ToKey: eventToKey,
FromKey: eventFromKey, FromKey: eventFromKey,
SubjectKey: eventSubjectKey, SubjectKey: eventSubjectKey,
MessageIDKey: eventMessageIDkey, MessageIDKey: eventMessageIDkey,
InReplyToKey: eventInReplyToKey, InReplyToKey: eventInReplyToKey,
ReferencesKey: eventReferencesKey,
} }
} }

View File

@@ -80,6 +80,7 @@ func (s *incomingSession) Data(r io.Reader) error {
email := utils.NewEmail( email := utils.NewEmail(
eml.GetHeader("Message-Id"), eml.GetHeader("Message-Id"),
eml.GetHeader("In-Reply-To"), eml.GetHeader("In-Reply-To"),
eml.GetHeader("References"),
eml.GetHeader("Subject"), eml.GetHeader("Subject"),
s.from, s.from,
s.to, s.to,
@@ -132,6 +133,7 @@ func (s *outgoingSession) Data(r io.Reader) error {
email := utils.NewEmail( email := utils.NewEmail(
eml.GetHeader("Message-Id"), eml.GetHeader("Message-Id"),
eml.GetHeader("In-Reply-To"), eml.GetHeader("In-Reply-To"),
eml.GetHeader("References"),
eml.GetHeader("Subject"), eml.GetHeader("Subject"),
s.from, s.from,
s.to, s.to,

View File

@@ -4,6 +4,7 @@ import (
"crypto" "crypto"
"crypto/x509" "crypto/x509"
"encoding/pem" "encoding/pem"
"fmt"
"net/mail" "net/mail"
"strings" "strings"
"time" "time"
@@ -23,15 +24,16 @@ type IncomingFilteringOptions interface {
// Email object // Email object
type Email struct { type Email struct {
Date string Date string
MessageID string MessageID string
InReplyTo string InReplyTo string
From string References string
To string From string
Subject string To string
Text string Subject string
HTML string Text string
Files []*File HTML string
Files []*File
} }
// ContentOptions represents settings that specify how an email is to be converted to a Matrix message // ContentOptions represents settings that specify how an email is to be converted to a Matrix message
@@ -44,11 +46,12 @@ type ContentOptions struct {
Threads bool Threads bool
// Keys // Keys
MessageIDKey string MessageIDKey string
InReplyToKey string InReplyToKey string
SubjectKey string ReferencesKey string
FromKey string SubjectKey string
ToKey string FromKey string
ToKey string
} }
// AddressValid checks if email address is valid // AddressValid checks if email address is valid
@@ -57,18 +60,24 @@ func AddressValid(email string) bool {
return err == nil return err == nil
} }
// MessageID generates email Message-Id from matrix event ID
func MessageID(eventID id.EventID, domain string) string {
return fmt.Sprintf("<%s@%s>", eventID, domain)
}
// NewEmail constructs Email object // NewEmail constructs Email object
func NewEmail(messageID, inReplyTo, subject, from, to, text, html string, files []*File) *Email { func NewEmail(messageID, inReplyTo, references, subject, from, to, text, html string, files []*File) *Email {
email := &Email{ email := &Email{
Date: time.Now().UTC().Format(time.RFC1123Z), Date: time.Now().UTC().Format(time.RFC1123Z),
MessageID: messageID, MessageID: messageID,
InReplyTo: inReplyTo, InReplyTo: inReplyTo,
From: from, References: references,
To: to, From: from,
Subject: subject, To: to,
Text: text, Subject: subject,
HTML: html, Text: text,
Files: files, HTML: html,
Files: files,
} }
if html != "" { if html != "" {
@@ -119,11 +128,12 @@ func (e *Email) Content(threadID id.EventID, options *ContentOptions) *event.Con
content := event.Content{ content := event.Content{
Raw: map[string]interface{}{ Raw: map[string]interface{}{
options.MessageIDKey: e.MessageID, options.MessageIDKey: e.MessageID,
options.InReplyToKey: e.InReplyTo, options.InReplyToKey: e.InReplyTo,
options.SubjectKey: e.Subject, options.ReferencesKey: e.References,
options.FromKey: e.From, options.SubjectKey: e.Subject,
options.ToKey: e.To, options.FromKey: e.From,
options.ToKey: e.To,
}, },
Parsed: parsed, Parsed: parsed,
} }
@@ -167,6 +177,12 @@ func (e *Email) Compose(privkey string) string {
data.WriteString("\r\n") data.WriteString("\r\n")
} }
if e.References != "" {
data.WriteString("References: ")
data.WriteString(e.References)
data.WriteString("\r\n")
}
data.WriteString("Subject: ") data.WriteString("Subject: ")
data.WriteString(e.Subject) data.WriteString(e.Subject)
data.WriteString("\r\n") data.WriteString("\r\n")