aboutsummaryrefslogtreecommitdiff
path: root/plugins/base
diff options
context:
space:
mode:
authorSimon Ser <contact@emersion.fr>2020-01-20 20:25:41 +0100
committerSimon Ser <contact@emersion.fr>2020-01-20 20:25:41 +0100
commit589b303f9ff49a87c6a64e51379207a809da6a48 (patch)
tree20b4f758326548a38152134b0ff4fd460550d9b1 /plugins/base
parent3340fcd63d3b07be6a52e0d483acb2a71fb21131 (diff)
downloadalps-589b303f9ff49a87c6a64e51379207a809da6a48.tar.gz
alps-589b303f9ff49a87c6a64e51379207a809da6a48.zip
plugins/base: append outgoing messages to Sent mailbox
And mark original message as answered. Closes: https://todo.sr.ht/~sircmpwn/koushin/15
Diffstat (limited to 'plugins/base')
-rwxr-xr-xplugins/base/imap.go36
-rw-r--r--plugins/base/routes.go50
2 files changed, 82 insertions, 4 deletions
diff --git a/plugins/base/imap.go b/plugins/base/imap.go
index 09ae305..0c39ca6 100755
--- a/plugins/base/imap.go
+++ b/plugins/base/imap.go
@@ -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)
+}
diff --git a/plugins/base/routes.go b/plugins/base/routes.go
index ad93c6f..961f25a 100644
--- a/plugins/base/routes.go
+++ b/plugins/base/routes.go
@@ -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