plugins/base: append outgoing messages to Sent mailbox

And mark original message as answered.

Closes: https://todo.sr.ht/~sircmpwn/koushin/15
This commit is contained in:
Simon Ser 2020-01-20 20:25:41 +01:00
parent 3340fcd63d
commit 589b303f9f
No known key found for this signature in database
GPG key ID: 0FDE7BE0E88F5E48
4 changed files with 85 additions and 4 deletions

1
go.mod
View file

@ -7,6 +7,7 @@ require (
github.com/chris-ramon/douceur v0.2.0
github.com/emersion/go-imap v1.0.3
github.com/emersion/go-imap-move v0.0.0-20190710073258-6e5a51a5b342
github.com/emersion/go-imap-specialuse v0.0.0-20161227184202-ba031ced6a62
github.com/emersion/go-message v0.11.1
github.com/emersion/go-sasl v0.0.0-20191210011802-430746ea8b9b
github.com/emersion/go-smtp v0.12.1

2
go.sum
View file

@ -14,6 +14,8 @@ github.com/emersion/go-imap v1.0.3 h1:5eEee8/DTSIPfliiWqwfvjPGkU8bBtvOy/Wx+eeXzO
github.com/emersion/go-imap v1.0.3/go.mod h1:yKASt+C3ZiDAiCSssxg9caIckWF/JG7ZQTO7GAmvicU=
github.com/emersion/go-imap-move v0.0.0-20190710073258-6e5a51a5b342 h1:5p1t3e1PomYgLWwEwhwEU5kVBwcyAcVrOpexv8AeZx0=
github.com/emersion/go-imap-move v0.0.0-20190710073258-6e5a51a5b342/go.mod h1:QuMaZcKFDVI0yCrnAbPLfbwllz1wtOrZH8/vZ5yzp4w=
github.com/emersion/go-imap-specialuse v0.0.0-20161227184202-ba031ced6a62 h1:4ZAfwfc8aDlj26kkEap1UDSwwDnJp9Ie8Uj1MSXAkPk=
github.com/emersion/go-imap-specialuse v0.0.0-20161227184202-ba031ced6a62/go.mod h1:/nybxhI8kXom8Tw6BrHMl42usALvka6meORflnnYwe4=
github.com/emersion/go-message v0.11.1 h1:0C/S4JIXDTSfXB1vpqdimAYyK4+79fgEAMQ0dSL+Kac=
github.com/emersion/go-message v0.11.1/go.mod h1:C4jnca5HOTo4bGN9YdqNQM9sITuT3Y0K6bSUw9RklvY=
github.com/emersion/go-sasl v0.0.0-20190817083125-240c8404624e h1:ba7YsgX5OV8FjGi5ZWml8Jng6oBrJAb3ahqWMJ5Ce8Q=

View file

@ -35,6 +35,30 @@ func listMailboxes(conn *imapclient.Client) ([]*imap.MailboxInfo, error) {
return mailboxes, nil
}
func getMailboxByAttribute(conn *imapclient.Client, attr string) (*imap.MailboxInfo, error) {
ch := make(chan *imap.MailboxInfo, 10)
done := make(chan error, 1)
go func() {
done <- conn.List("", "%", ch)
}()
var mailbox *imap.MailboxInfo
for mbox := range ch {
for _, a := range mbox.Attributes {
if attr == a {
mailbox = mbox
break
}
}
}
if err := <-done; err != nil {
return nil, fmt.Errorf("failed to get mailbox with attribute %q: %v", attr, err)
}
return mailbox, nil
}
func ensureMailboxSelected(conn *imapclient.Client, mboxName string) error {
mbox := conn.Mailbox()
if mbox == nil || mbox.Name != mboxName {
@ -339,3 +363,15 @@ func getMessagePart(conn *imapclient.Client, mboxName string, uid uint32, partPa
return &IMAPMessage{msg}, part, nil
}
func markMessageAnswered(conn *imapclient.Client, mboxName string, uid uint32) error {
if err := ensureMailboxSelected(conn, mboxName); err != nil {
return err
}
seqSet := new(imap.SeqSet)
seqSet.AddNum(uid)
item := imap.FormatFlagsOp(imap.AddFlags, true)
flags := []interface{}{imap.AnsweredFlag}
return conn.UidStore(seqSet, item, flags, nil)
}

View file

@ -1,6 +1,7 @@
package koushinbase
import (
"bytes"
"fmt"
"io/ioutil"
"mime"
@ -8,10 +9,12 @@ import (
"net/url"
"strconv"
"strings"
"time"
"git.sr.ht/~emersion/koushin"
"github.com/emersion/go-imap"
imapmove "github.com/emersion/go-imap-move"
imapspecialuse "github.com/emersion/go-imap-specialuse"
imapclient "github.com/emersion/go-imap/client"
"github.com/emersion/go-message"
"github.com/emersion/go-smtp"
@ -289,12 +292,19 @@ func handleCompose(ctx *koushin.Context) error {
msg.Text = ctx.QueryParam("body")
msg.InReplyTo = ctx.QueryParam("in-reply-to")
if ctx.Request().Method == http.MethodGet && ctx.Param("uid") != "" {
var inReplyToMboxName string
var inReplyToUid uint32
if ctx.Param("uid") != "" {
// This is a reply
mboxName, uid, err := parseMboxAndUid(ctx.Param("mbox"), ctx.Param("uid"))
var err error
inReplyToMboxName, inReplyToUid, err = parseMboxAndUid(ctx.Param("mbox"), ctx.Param("uid"))
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err)
}
}
if ctx.Request().Method == http.MethodGet && inReplyToUid != 0 {
// Populate fields from original message
partPath, err := parsePartPath(ctx.QueryParam("part"))
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err)
@ -304,7 +314,7 @@ func handleCompose(ctx *koushin.Context) error {
var part *message.Entity
err = ctx.Session.DoIMAP(func(c *imapclient.Client) error {
var err error
inReplyTo, part, err = getMessagePart(c, mboxName, uid, partPath)
inReplyTo, part, err = getMessagePart(c, inReplyToMboxName, inReplyToUid, partPath)
return err
})
if err != nil {
@ -364,7 +374,39 @@ func handleCompose(ctx *koushin.Context) error {
if _, ok := err.(koushin.AuthError); ok {
return echo.NewHTTPError(http.StatusForbidden, err)
}
return err
return fmt.Errorf("failed to send message: %v", err)
}
if inReplyToUid != 0 {
err = ctx.Session.DoIMAP(func(c *imapclient.Client) error {
return markMessageAnswered(c, inReplyToMboxName, inReplyToUid)
})
if err != nil {
return fmt.Errorf("failed to mark original message as answered: %v", err)
}
}
err = ctx.Session.DoIMAP(func(c *imapclient.Client) error {
mbox, err := getMailboxByAttribute(c, imapspecialuse.Sent)
if err != nil {
return err
}
if mbox == nil {
return nil
}
// IMAP needs to know in advance the final size of the message, so
// there's no way around storing it in a buffer here.
var buf bytes.Buffer
if err := msg.WriteTo(&buf); err != nil {
return err
}
flags := []string{imap.SeenFlag}
return c.Append(mbox.Name, flags, time.Now(), &buf)
})
if err != nil {
return fmt.Errorf("failed to save message to Sent mailbox: %v", err)
}
// TODO: append to IMAP Sent mailbox