plugins/base: save message as draft
This commit is contained in:
parent
267999b6e5
commit
bfc617b702
4 changed files with 98 additions and 45 deletions
|
@ -2,12 +2,15 @@ package koushinbase
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/emersion/go-imap"
|
"github.com/emersion/go-imap"
|
||||||
|
imapspecialuse "github.com/emersion/go-imap-specialuse"
|
||||||
imapclient "github.com/emersion/go-imap/client"
|
imapclient "github.com/emersion/go-imap/client"
|
||||||
"github.com/emersion/go-message"
|
"github.com/emersion/go-message"
|
||||||
"github.com/emersion/go-message/textproto"
|
"github.com/emersion/go-message/textproto"
|
||||||
|
@ -375,3 +378,45 @@ func markMessageAnswered(conn *imapclient.Client, mboxName string, uid uint32) e
|
||||||
flags := []interface{}{imap.AnsweredFlag}
|
flags := []interface{}{imap.AnsweredFlag}
|
||||||
return conn.UidStore(seqSet, item, flags, nil)
|
return conn.UidStore(seqSet, item, flags, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type mailboxType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
mailboxSent mailboxType = iota
|
||||||
|
mailboxDrafts
|
||||||
|
)
|
||||||
|
|
||||||
|
func appendMessage(c *imapclient.Client, msg *OutgoingMessage, mboxType mailboxType) (saved bool, err error) {
|
||||||
|
var mboxAttr string
|
||||||
|
switch mboxType {
|
||||||
|
case mailboxSent:
|
||||||
|
mboxAttr = imapspecialuse.Sent
|
||||||
|
case mailboxDrafts:
|
||||||
|
mboxAttr = imapspecialuse.Drafts
|
||||||
|
}
|
||||||
|
|
||||||
|
mbox, err := getMailboxByAttribute(c, mboxAttr)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if mbox == nil {
|
||||||
|
return false, 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 false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
flags := []string{imap.SeenFlag}
|
||||||
|
if mboxType == mailboxDrafts {
|
||||||
|
flags = append(flags, imap.DraftFlag)
|
||||||
|
}
|
||||||
|
if err := c.Append(mbox.Name, flags, time.Now(), &buf); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
|
@ -26,6 +26,7 @@
|
||||||
<label for="attachments">Attachments:</label>
|
<label for="attachments">Attachments:</label>
|
||||||
<input type="file" name="attachments" id="attachments" multiple>
|
<input type="file" name="attachments" id="attachments" multiple>
|
||||||
<br><br>
|
<br><br>
|
||||||
|
<input type="submit" name="save_as_draft" value="Save as draft">
|
||||||
<input type="submit" value="Send">
|
<input type="submit" value="Send">
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package koushinbase
|
package koushinbase
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"mime"
|
"mime"
|
||||||
|
@ -9,12 +8,10 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"git.sr.ht/~emersion/koushin"
|
"git.sr.ht/~emersion/koushin"
|
||||||
"github.com/emersion/go-imap"
|
"github.com/emersion/go-imap"
|
||||||
imapmove "github.com/emersion/go-imap-move"
|
imapmove "github.com/emersion/go-imap-move"
|
||||||
imapspecialuse "github.com/emersion/go-imap-specialuse"
|
|
||||||
imapclient "github.com/emersion/go-imap/client"
|
imapclient "github.com/emersion/go-imap/client"
|
||||||
"github.com/emersion/go-message"
|
"github.com/emersion/go-message"
|
||||||
"github.com/emersion/go-smtp"
|
"github.com/emersion/go-smtp"
|
||||||
|
@ -281,6 +278,39 @@ type ComposeRenderData struct {
|
||||||
Message *OutgoingMessage
|
Message *OutgoingMessage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Send message, append it to the Sent mailbox, mark the original message as
|
||||||
|
// answered
|
||||||
|
func submitCompose(ctx *koushin.Context, msg *OutgoingMessage, inReplyToMboxName string, inReplyToUid uint32) error {
|
||||||
|
err := ctx.Session.DoSMTP(func(c *smtp.Client) error {
|
||||||
|
return sendMessage(c, msg)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
if _, ok := err.(koushin.AuthError); ok {
|
||||||
|
return echo.NewHTTPError(http.StatusForbidden, 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 {
|
||||||
|
_, err := appendMessage(c, msg, mailboxSent)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to save message to Sent mailbox: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctx.Redirect(http.StatusFound, "/mailbox/INBOX")
|
||||||
|
}
|
||||||
|
|
||||||
func handleCompose(ctx *koushin.Context) error {
|
func handleCompose(ctx *koushin.Context) error {
|
||||||
var msg OutgoingMessage
|
var msg OutgoingMessage
|
||||||
if strings.ContainsRune(ctx.Session.Username(), '@') {
|
if strings.ContainsRune(ctx.Session.Username(), '@') {
|
||||||
|
@ -355,6 +385,12 @@ func handleCompose(ctx *koushin.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.Request().Method == http.MethodPost {
|
if ctx.Request().Method == http.MethodPost {
|
||||||
|
formParams, err := ctx.FormParams()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to parse form: %v", err)
|
||||||
|
}
|
||||||
|
_, saveAsDraft := formParams["save_as_draft"]
|
||||||
|
|
||||||
msg.From = ctx.FormValue("from")
|
msg.From = ctx.FormValue("from")
|
||||||
msg.To = parseAddressList(ctx.FormValue("to"))
|
msg.To = parseAddressList(ctx.FormValue("to"))
|
||||||
msg.Subject = ctx.FormValue("subject")
|
msg.Subject = ctx.FormValue("subject")
|
||||||
|
@ -367,52 +403,23 @@ func handleCompose(ctx *koushin.Context) error {
|
||||||
}
|
}
|
||||||
msg.Attachments = form.File["attachments"]
|
msg.Attachments = form.File["attachments"]
|
||||||
|
|
||||||
err = ctx.Session.DoSMTP(func(c *smtp.Client) error {
|
if saveAsDraft {
|
||||||
return sendMessage(c, &msg)
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
if _, ok := err.(koushin.AuthError); ok {
|
|
||||||
return echo.NewHTTPError(http.StatusForbidden, err)
|
|
||||||
}
|
|
||||||
return fmt.Errorf("failed to send message: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if inReplyToUid != 0 {
|
|
||||||
err = ctx.Session.DoIMAP(func(c *imapclient.Client) error {
|
err = ctx.Session.DoIMAP(func(c *imapclient.Client) error {
|
||||||
return markMessageAnswered(c, inReplyToMboxName, inReplyToUid)
|
copied, err := appendMessage(c, &msg, mailboxDrafts)
|
||||||
})
|
|
||||||
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if mbox == nil {
|
if !copied {
|
||||||
|
return fmt.Errorf("no Draft mailbox found")
|
||||||
|
}
|
||||||
return 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 {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to save message to Sent mailbox: %v", err)
|
return fmt.Errorf("failed to save message to Draft mailbox: %v", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return submitCompose(ctx, &msg, inReplyToMboxName, inReplyToUid)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: append to IMAP Sent mailbox
|
|
||||||
// TODO: add \Answered flag to original IMAP message
|
|
||||||
|
|
||||||
return ctx.Redirect(http.StatusFound, "/mailbox/INBOX")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ctx.Render(http.StatusOK, "compose.html", &ComposeRenderData{
|
return ctx.Render(http.StatusOK, "compose.html", &ComposeRenderData{
|
||||||
|
|
|
@ -5,10 +5,10 @@ import (
|
||||||
"html/template"
|
"html/template"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
|
"git.sr.ht/~emersion/koushin"
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
"github.com/yuin/gopher-lua"
|
"github.com/yuin/gopher-lua"
|
||||||
"layeh.com/gopher-luar"
|
"layeh.com/gopher-luar"
|
||||||
"git.sr.ht/~emersion/koushin"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type luaRoute struct {
|
type luaRoute struct {
|
||||||
|
|
Loading…
Add table
Reference in a new issue