Factor IMAP-specific structs out of UI models

Before, we were using several IMAP-specific concepts to represent
information being displayed in the UI. Factor these structures out of
the IMAP package to make it easier for other backends to provide the
required information.
This commit is contained in:
Ben Burwell 2019-07-07 22:43:58 -04:00 committed by Drew DeVault
parent 88c379dcba
commit c610c3cd9d
10 changed files with 211 additions and 108 deletions

View file

@ -9,12 +9,11 @@ import (
"strings" "strings"
"git.sr.ht/~sircmpwn/getopt" "git.sr.ht/~sircmpwn/getopt"
"github.com/emersion/go-imap"
"github.com/emersion/go-message" "github.com/emersion/go-message"
_ "github.com/emersion/go-message/charset" _ "github.com/emersion/go-message/charset"
"github.com/emersion/go-message/mail" "github.com/emersion/go-message/mail"
"git.sr.ht/~sircmpwn/aerc/lib" "git.sr.ht/~sircmpwn/aerc/models"
"git.sr.ht/~sircmpwn/aerc/widgets" "git.sr.ht/~sircmpwn/aerc/widgets"
) )
@ -67,7 +66,7 @@ func (_ reply) Execute(aerc *widgets.Aerc, args []string) error {
var ( var (
to []string to []string
cc []string cc []string
toList []*imap.Address toList []*models.Address
) )
if args[0] == "reply" { if args[0] == "reply" {
if len(msg.Envelope.ReplyTo) != 0 { if len(msg.Envelope.ReplyTo) != 0 {
@ -76,24 +75,23 @@ func (_ reply) Execute(aerc *widgets.Aerc, args []string) error {
toList = msg.Envelope.From toList = msg.Envelope.From
} }
for _, addr := range toList { for _, addr := range toList {
if addr.PersonalName != "" { if addr.Name != "" {
to = append(to, fmt.Sprintf("%s <%s@%s>", to = append(to, fmt.Sprintf("%s <%s@%s>",
addr.PersonalName, addr.MailboxName, addr.HostName)) addr.Name, addr.Mailbox, addr.Host))
} else { } else {
to = append(to, fmt.Sprintf("<%s@%s>", to = append(to, fmt.Sprintf("<%s@%s>", addr.Mailbox, addr.Host))
addr.MailboxName, addr.HostName))
} }
} }
if replyAll { if replyAll {
for _, addr := range msg.Envelope.Cc { for _, addr := range msg.Envelope.Cc {
cc = append(cc, lib.FormatAddress(addr)) cc = append(cc, addr.Format())
} }
for _, addr := range msg.Envelope.To { for _, addr := range msg.Envelope.To {
address := fmt.Sprintf("%s@%s", addr.MailboxName, addr.HostName) address := fmt.Sprintf("%s@%s", addr.Mailbox, addr.Host)
if address == us.Address { if address == us.Address {
continue continue
} }
to = append(to, lib.FormatAddress(addr)) to = append(to, addr.Format())
} }
} }
} }
@ -163,7 +161,7 @@ func (_ reply) Execute(aerc *widgets.Aerc, args []string) error {
go composer.SetContents(pipeout) go composer.SetContents(pipeout)
// TODO: Let user customize the date format used here // TODO: Let user customize the date format used here
io.WriteString(pipein, fmt.Sprintf("Forwarded message from %s on %s:\n\n", io.WriteString(pipein, fmt.Sprintf("Forwarded message from %s on %s:\n\n",
msg.Envelope.From[0].PersonalName, msg.Envelope.From[0].Name,
msg.Envelope.Date.Format("Mon Jan 2, 2006 at 3:04 PM"))) msg.Envelope.Date.Format("Mon Jan 2, 2006 at 3:04 PM")))
for scanner.Scan() { for scanner.Scan() {
io.WriteString(pipein, fmt.Sprintf("%s\n", scanner.Text())) io.WriteString(pipein, fmt.Sprintf("%s\n", scanner.Text()))
@ -176,7 +174,7 @@ func (_ reply) Execute(aerc *widgets.Aerc, args []string) error {
if quote { if quote {
var ( var (
path []int path []int
part *imap.BodyStructure part *models.BodyStructure
) )
if len(msg.BodyStructure.Parts) != 0 { if len(msg.BodyStructure.Parts) != 0 {
part, path = findPlaintext(msg.BodyStructure, path) part, path = findPlaintext(msg.BodyStructure, path)
@ -212,7 +210,7 @@ func (_ reply) Execute(aerc *widgets.Aerc, args []string) error {
// TODO: Let user customize the date format used here // TODO: Let user customize the date format used here
io.WriteString(pipein, fmt.Sprintf("On %s %s wrote:\n", io.WriteString(pipein, fmt.Sprintf("On %s %s wrote:\n",
msg.Envelope.Date.Format("Mon Jan 2, 2006 at 3:04 PM"), msg.Envelope.Date.Format("Mon Jan 2, 2006 at 3:04 PM"),
msg.Envelope.From[0].PersonalName)) msg.Envelope.From[0].Name))
for scanner.Scan() { for scanner.Scan() {
io.WriteString(pipein, fmt.Sprintf("> %s\n", scanner.Text())) io.WriteString(pipein, fmt.Sprintf("> %s\n", scanner.Text()))
} }
@ -228,8 +226,8 @@ func (_ reply) Execute(aerc *widgets.Aerc, args []string) error {
return nil return nil
} }
func findPlaintext(bs *imap.BodyStructure, func findPlaintext(bs *models.BodyStructure,
path []int) (*imap.BodyStructure, []int) { path []int) (*models.BodyStructure, []int) {
for i, part := range bs.Parts { for i, part := range bs.Parts {
cur := append(path, i+1) cur := append(path, i+1)

View file

@ -1,40 +0,0 @@
package lib
import (
"bytes"
"fmt"
"regexp"
"strings"
"github.com/emersion/go-imap"
)
var (
atom *regexp.Regexp = regexp.MustCompile("^[a-z0-9!#$%7'*+-/=?^_`{}|~ ]+$")
)
func FormatAddresses(addrs []*imap.Address) string {
val := bytes.Buffer{}
for i, addr := range addrs {
val.WriteString(FormatAddress(addr))
if i != len(addrs)-1 {
val.WriteString(", ")
}
}
return val.String()
}
func FormatAddress(addr *imap.Address) string {
if addr.PersonalName != "" {
if atom.MatchString(addr.PersonalName) {
return fmt.Sprintf("%s <%s@%s>",
addr.PersonalName, addr.MailboxName, addr.HostName)
} else {
return fmt.Sprintf("\"%s\" <%s@%s>",
strings.ReplaceAll(addr.PersonalName, "\"", "'"),
addr.MailboxName, addr.HostName)
}
} else {
return fmt.Sprintf("<%s@%s>", addr.MailboxName, addr.HostName)
}
}

View file

@ -6,8 +6,6 @@ import (
"strings" "strings"
"unicode" "unicode"
"github.com/emersion/go-imap"
"git.sr.ht/~sircmpwn/aerc/config" "git.sr.ht/~sircmpwn/aerc/config"
"git.sr.ht/~sircmpwn/aerc/models" "git.sr.ht/~sircmpwn/aerc/models"
) )
@ -70,10 +68,9 @@ func ParseIndexFormat(conf *config.AercConfig, number int,
} }
addr := msg.Envelope.From[0] addr := msg.Envelope.From[0]
retval = append(retval, 's') retval = append(retval, 's')
args = append(args, fmt.Sprintf("%s@%s", addr.MailboxName, args = append(args, fmt.Sprintf("%s@%s", addr.Mailbox, addr.Host))
addr.HostName))
case 'A': case 'A':
var addr *imap.Address var addr *models.Address
if len(msg.Envelope.ReplyTo) == 0 { if len(msg.Envelope.ReplyTo) == 0 {
if len(msg.Envelope.From) == 0 { if len(msg.Envelope.From) == 0 {
return "", nil, return "", nil,
@ -85,8 +82,7 @@ func ParseIndexFormat(conf *config.AercConfig, number int,
addr = msg.Envelope.ReplyTo[0] addr = msg.Envelope.ReplyTo[0]
} }
retval = append(retval, 's') retval = append(retval, 's')
args = append(args, fmt.Sprintf("%s@%s", addr.MailboxName, args = append(args, fmt.Sprintf("%s@%s", addr.Mailbox, addr.Host))
addr.HostName))
case 'C': case 'C':
retval = append(retval, 'd') retval = append(retval, 'd')
args = append(args, number) args = append(args, number)
@ -100,7 +96,7 @@ func ParseIndexFormat(conf *config.AercConfig, number int,
if len(msg.Envelope.From) == 0 { if len(msg.Envelope.From) == 0 {
return "", nil, errors.New("found no address for sender") return "", nil, errors.New("found no address for sender")
} }
addr := FormatAddress(msg.Envelope.From[0]) addr := msg.Envelope.From[0].Format()
retval = append(retval, 's') retval = append(retval, 's')
args = append(args, addr) args = append(args, addr)
case 'F': case 'F':
@ -111,11 +107,10 @@ func ParseIndexFormat(conf *config.AercConfig, number int,
// TODO: handle case when sender is current user. Then // TODO: handle case when sender is current user. Then
// use recipient's name // use recipient's name
var val string var val string
if addr.PersonalName != "" { if addr.Name != "" {
val = addr.PersonalName val = addr.Name
} else { } else {
val = fmt.Sprintf("%s@%s", val = fmt.Sprintf("%s@%s", addr.Mailbox, addr.Host)
addr.MailboxName, addr.HostName)
} }
retval = append(retval, 's') retval = append(retval, 's')
args = append(args, val) args = append(args, val)
@ -129,20 +124,19 @@ func ParseIndexFormat(conf *config.AercConfig, number int,
} }
addr := msg.Envelope.From[0] addr := msg.Envelope.From[0]
var val string var val string
if addr.PersonalName != "" { if addr.Name != "" {
val = addr.PersonalName val = addr.Name
} else { } else {
val = fmt.Sprintf("%s@%s", val = fmt.Sprintf("%s@%s", addr.Mailbox, addr.Host)
addr.MailboxName, addr.HostName)
} }
retval = append(retval, 's') retval = append(retval, 's')
args = append(args, val) args = append(args, val)
case 'r': case 'r':
addrs := FormatAddresses(msg.Envelope.To) addrs := models.FormatAddresses(msg.Envelope.To)
retval = append(retval, 's') retval = append(retval, 's')
args = append(args, addrs) args = append(args, addrs)
case 'R': case 'R':
addrs := FormatAddresses(msg.Envelope.Cc) addrs := models.FormatAddresses(msg.Envelope.Cc)
retval = append(retval, 's') retval = append(retval, 's')
args = append(args, addrs) args = append(args, addrs)
case 's': case 's':
@ -154,16 +148,16 @@ func ParseIndexFormat(conf *config.AercConfig, number int,
} }
addr := msg.Envelope.From[0] addr := msg.Envelope.From[0]
retval = append(retval, 's') retval = append(retval, 's')
args = append(args, addr.MailboxName) args = append(args, addr.Mailbox)
case 'v': case 'v':
if len(msg.Envelope.From) == 0 { if len(msg.Envelope.From) == 0 {
return "", nil, errors.New("found no address for sender") return "", nil, errors.New("found no address for sender")
} }
addr := msg.Envelope.From[0] addr := msg.Envelope.From[0]
// check if message is from current user // check if message is from current user
if addr.PersonalName != "" { if addr.Name != "" {
retval = append(retval, 's') retval = append(retval, 's')
args = append(args, strings.Split(addr.PersonalName, " ")[0]) args = append(args, strings.Split(addr.Name, " ")[0])
} }
case 'Z': case 'Z':
// calculate all flags // calculate all flags
@ -171,18 +165,18 @@ func ParseIndexFormat(conf *config.AercConfig, number int,
var delFlag = "" var delFlag = ""
var flaggedFlag = "" var flaggedFlag = ""
for _, flag := range msg.Flags { for _, flag := range msg.Flags {
if flag == imap.SeenFlag { if flag == models.SeenFlag {
readFlag = "O" // message is old readFlag = "O" // message is old
} else if flag == imap.RecentFlag { } else if flag == models.RecentFlag {
readFlag = "N" // message is new readFlag = "N" // message is new
} else if flag == imap.AnsweredFlag { } else if flag == models.AnsweredFlag {
readFlag = "r" // message has been replied to readFlag = "r" // message has been replied to
} }
if flag == imap.DeletedFlag { if flag == models.DeletedFlag {
delFlag = "D" delFlag = "D"
// TODO: check if attachments // TODO: check if attachments
} }
if flag == imap.FlaggedFlag { if flag == models.FlaggedFlag {
flaggedFlag = "!" flaggedFlag = "!"
} }
// TODO: check gpg stuff // TODO: check gpg stuff

View file

@ -1,13 +1,37 @@
package models package models
import ( import (
"bytes"
"fmt"
"io" "io"
"regexp"
"strings"
"time" "time"
"github.com/emersion/go-imap"
"github.com/emersion/go-message/mail" "github.com/emersion/go-message/mail"
) )
// Flag is an abstraction around the different flags which can be present in
// different email backends and represents a flag that we use in the UI.
type Flag int
const (
// SeenFlag marks a message as having been seen previously
SeenFlag Flag = iota
// RecentFlag marks a message as being recent
RecentFlag
// AnsweredFlag marks a message as having been replied to
AnsweredFlag
// DeletedFlag marks a message as having been deleted
DeletedFlag
// FlaggedFlag marks a message with a user flag
FlaggedFlag
)
type Directory struct { type Directory struct {
Name string Name string
Attributes []string Attributes []string
@ -30,9 +54,9 @@ type DirectoryInfo struct {
// A MessageInfo holds information about the structure of a message // A MessageInfo holds information about the structure of a message
type MessageInfo struct { type MessageInfo struct {
BodyStructure *imap.BodyStructure BodyStructure *BodyStructure
Envelope *imap.Envelope Envelope *Envelope
Flags []string Flags []Flag
InternalDate time.Time InternalDate time.Time
RFC822Headers *mail.Header RFC822Headers *mail.Header
Size uint32 Size uint32
@ -50,3 +74,59 @@ type FullMessage struct {
Reader io.Reader Reader io.Reader
Uid uint32 Uid uint32
} }
type BodyStructure struct {
MIMEType string
MIMESubType string
Params map[string]string
Description string
Encoding string
Parts []*BodyStructure
Disposition string
DispositionParams map[string]string
}
type Envelope struct {
Date time.Time
Subject string
From []*Address
ReplyTo []*Address
To []*Address
Cc []*Address
Bcc []*Address
MessageId string
}
type Address struct {
Name string
Mailbox string
Host string
}
var atom *regexp.Regexp = regexp.MustCompile("^[a-z0-9!#$%7'*+-/=?^_`{}|~ ]+$")
func (a Address) Format() string {
if a.Name != "" {
if atom.MatchString(a.Name) {
return fmt.Sprintf("%s <%s@%s>", a.Name, a.Mailbox, a.Host)
} else {
return fmt.Sprintf("\"%s\" <%s@%s>",
strings.ReplaceAll(a.Name, "\"", "'"),
a.Mailbox, a.Host)
}
} else {
return fmt.Sprintf("<%s@%s>", a.Mailbox, a.Host)
}
}
// FormatAddresses formats a list of addresses, separating each by a comma
func FormatAddresses(addrs []*Address) string {
val := bytes.Buffer{}
for i, addr := range addrs {
val.WriteString(addr.Format())
if i != len(addrs)-1 {
val.WriteString(", ")
}
}
return val.String()
}

View file

@ -4,7 +4,6 @@ import (
"fmt" "fmt"
"log" "log"
"github.com/emersion/go-imap"
"github.com/gdamore/tcell" "github.com/gdamore/tcell"
"github.com/mattn/go-runewidth" "github.com/mattn/go-runewidth"
@ -86,7 +85,7 @@ func (ml *MessageList) Draw(ctx *ui.Context) {
// unread message // unread message
seen := false seen := false
for _, flag := range msg.Flags { for _, flag := range msg.Flags {
if flag == imap.SeenFlag { if flag == models.SeenFlag {
seen = true seen = true
} }
} }

View file

@ -9,7 +9,6 @@ import (
"strings" "strings"
"github.com/danwakefield/fnmatch" "github.com/danwakefield/fnmatch"
"github.com/emersion/go-imap"
"github.com/emersion/go-message" "github.com/emersion/go-message"
_ "github.com/emersion/go-message/charset" _ "github.com/emersion/go-message/charset"
"github.com/emersion/go-message/mail" "github.com/emersion/go-message/mail"
@ -66,12 +65,12 @@ func NewMessageViewer(acct *AccountView, conf *config.AercConfig,
headers.AddChild( headers.AddChild(
&HeaderView{ &HeaderView{
Name: "From", Name: "From",
Value: lib.FormatAddresses(msg.Envelope.From), Value: models.FormatAddresses(msg.Envelope.From),
}).At(0, 0) }).At(0, 0)
headers.AddChild( headers.AddChild(
&HeaderView{ &HeaderView{
Name: "To", Name: "To",
Value: lib.FormatAddresses(msg.Envelope.To), Value: models.FormatAddresses(msg.Envelope.To),
}).At(0, 1) }).At(0, 1)
headers.AddChild( headers.AddChild(
&HeaderView{ &HeaderView{
@ -112,7 +111,7 @@ handle_error:
} }
func enumerateParts(conf *config.AercConfig, store *lib.MessageStore, func enumerateParts(conf *config.AercConfig, store *lib.MessageStore,
msg *models.MessageInfo, body *imap.BodyStructure, msg *models.MessageInfo, body *models.BodyStructure,
showHeaders bool, index []int) ([]*PartViewer, error) { showHeaders bool, index []int) ([]*PartViewer, error) {
var parts []*PartViewer var parts []*PartViewer
@ -324,7 +323,7 @@ type PartViewer struct {
msg *models.MessageInfo msg *models.MessageInfo
pager *exec.Cmd pager *exec.Cmd
pagerin io.WriteCloser pagerin io.WriteCloser
part *imap.BodyStructure part *models.BodyStructure
showHeaders bool showHeaders bool
sink io.WriteCloser sink io.WriteCloser
source io.Reader source io.Reader
@ -334,7 +333,7 @@ type PartViewer struct {
func NewPartViewer(conf *config.AercConfig, func NewPartViewer(conf *config.AercConfig,
store *lib.MessageStore, msg *models.MessageInfo, store *lib.MessageStore, msg *models.MessageInfo,
part *imap.BodyStructure, showHeaders bool, part *models.BodyStructure, showHeaders bool,
index []int) (*PartViewer, error) { index []int) (*PartViewer, error) {
var ( var (
@ -365,11 +364,11 @@ func NewPartViewer(conf *config.AercConfig,
case "subject": case "subject":
header = msg.Envelope.Subject header = msg.Envelope.Subject
case "from": case "from":
header = lib.FormatAddresses(msg.Envelope.From) header = models.FormatAddresses(msg.Envelope.From)
case "to": case "to":
header = lib.FormatAddresses(msg.Envelope.To) header = models.FormatAddresses(msg.Envelope.To)
case "cc": case "cc":
header = lib.FormatAddresses(msg.Envelope.Cc) header = models.FormatAddresses(msg.Envelope.Cc)
} }
if f.Regex.Match([]byte(header)) { if f.Regex.Match([]byte(header)) {
filter = exec.Command("sh", "-c", f.Command) filter = exec.Command("sh", "-c", f.Command)

View file

@ -1,8 +1,6 @@
package widgets package widgets
import ( import (
"github.com/emersion/go-imap"
"git.sr.ht/~sircmpwn/aerc/lib" "git.sr.ht/~sircmpwn/aerc/lib"
"git.sr.ht/~sircmpwn/aerc/lib/ui" "git.sr.ht/~sircmpwn/aerc/lib/ui"
"git.sr.ht/~sircmpwn/aerc/models" "git.sr.ht/~sircmpwn/aerc/models"
@ -10,8 +8,8 @@ import (
type PartInfo struct { type PartInfo struct {
Index []int Index []int
Msg *types.MessageInfo Msg *models.MessageInfo
Part *imap.BodyStructure Part *models.BodyStructure
Store *lib.MessageStore Store *lib.MessageStore
} }

View file

@ -82,9 +82,9 @@ func (imapw *IMAPWorker) handleFetchMessages(
imapw.worker.PostMessage(&types.MessageInfo{ imapw.worker.PostMessage(&types.MessageInfo{
Message: types.RespondTo(msg), Message: types.RespondTo(msg),
Info: &models.MessageInfo{ Info: &models.MessageInfo{
BodyStructure: _msg.BodyStructure, BodyStructure: translateBodyStructure(_msg.BodyStructure),
Envelope: _msg.Envelope, Envelope: translateEnvelope(_msg.Envelope),
Flags: _msg.Flags, Flags: translateFlags(_msg.Flags),
InternalDate: _msg.InternalDate, InternalDate: _msg.InternalDate,
RFC822Headers: header, RFC822Headers: header,
Uid: _msg.Uid, Uid: _msg.Uid,
@ -103,7 +103,7 @@ func (imapw *IMAPWorker) handleFetchMessages(
imapw.worker.PostMessage(&types.MessageInfo{ imapw.worker.PostMessage(&types.MessageInfo{
Message: types.RespondTo(msg), Message: types.RespondTo(msg),
Info: &models.MessageInfo{ Info: &models.MessageInfo{
Flags: _msg.Flags, Flags: translateFlags(_msg.Flags),
Uid: _msg.Uid, Uid: _msg.Uid,
}, },
}, nil) }, nil)
@ -120,7 +120,7 @@ func (imapw *IMAPWorker) handleFetchMessages(
imapw.worker.PostMessage(&types.MessageInfo{ imapw.worker.PostMessage(&types.MessageInfo{
Message: types.RespondTo(msg), Message: types.RespondTo(msg),
Info: &models.MessageInfo{ Info: &models.MessageInfo{
Flags: _msg.Flags, Flags: translateFlags(_msg.Flags),
Uid: _msg.Uid, Uid: _msg.Uid,
}, },
}, nil) }, nil)

View file

@ -2,6 +2,8 @@ package imap
import ( import (
"github.com/emersion/go-imap" "github.com/emersion/go-imap"
"git.sr.ht/~sircmpwn/aerc/models"
) )
func toSeqSet(uids []uint32) *imap.SeqSet { func toSeqSet(uids []uint32) *imap.SeqSet {
@ -11,3 +13,76 @@ func toSeqSet(uids []uint32) *imap.SeqSet {
} }
return &set return &set
} }
func translateBodyStructure(bs *imap.BodyStructure) *models.BodyStructure {
if bs == nil {
return nil
}
var parts []*models.BodyStructure
for _, part := range bs.Parts {
parts = append(parts, translateBodyStructure(part))
}
return &models.BodyStructure{
MIMEType: bs.MIMEType,
MIMESubType: bs.MIMESubType,
Params: bs.Params,
Description: bs.Description,
Encoding: bs.Encoding,
Parts: parts,
Disposition: bs.Disposition,
DispositionParams: bs.DispositionParams,
}
}
func translateEnvelope(e *imap.Envelope) *models.Envelope {
if e == nil {
return nil
}
return &models.Envelope{
Date: e.Date,
Subject: e.Subject,
From: translateAddresses(e.From),
ReplyTo: translateAddresses(e.ReplyTo),
To: translateAddresses(e.To),
Cc: translateAddresses(e.Cc),
Bcc: translateAddresses(e.Bcc),
MessageId: e.MessageId,
}
}
func translateAddress(a *imap.Address) *models.Address {
if a == nil {
return nil
}
return &models.Address{
Name: a.PersonalName,
Mailbox: a.MailboxName,
Host: a.HostName,
}
}
func translateAddresses(addrs []*imap.Address) []*models.Address {
var converted []*models.Address
for _, addr := range addrs {
converted = append(converted, translateAddress(addr))
}
return converted
}
var flagMap = map[string]models.Flag{
imap.SeenFlag: models.SeenFlag,
imap.RecentFlag: models.RecentFlag,
imap.AnsweredFlag: models.AnsweredFlag,
imap.DeletedFlag: models.DeletedFlag,
imap.FlaggedFlag: models.FlaggedFlag,
}
func translateFlags(imapFlags []string) []models.Flag {
var flags []models.Flag
for _, imapFlag := range imapFlags {
if flag, ok := flagMap[imapFlag]; ok {
flags = append(flags, flag)
}
}
return flags
}

View file

@ -187,9 +187,9 @@ func (w *IMAPWorker) handleImapUpdate(update client.Update) {
} }
w.worker.PostMessage(&types.MessageInfo{ w.worker.PostMessage(&types.MessageInfo{
Info: &models.MessageInfo{ Info: &models.MessageInfo{
BodyStructure: msg.BodyStructure, BodyStructure: translateBodyStructure(msg.BodyStructure),
Envelope: msg.Envelope, Envelope: translateEnvelope(msg.Envelope),
Flags: msg.Flags, Flags: translateFlags(msg.Flags),
InternalDate: msg.InternalDate, InternalDate: msg.InternalDate,
Uid: msg.Uid, Uid: msg.Uid,
}, },