package jmap import ( "errors" "fmt" "sort" "strings" "git.sr.ht/~rjarry/aerc/models" "git.sr.ht/~rockorager/go-jmap" "git.sr.ht/~rockorager/go-jmap/mail" "git.sr.ht/~rockorager/go-jmap/mail/email" "git.sr.ht/~rockorager/go-jmap/mail/mailbox" msgmail "github.com/emersion/go-message/mail" ) func (w *JMAPWorker) translateMsgInfo(m *email.Email) *models.MessageInfo { env := &models.Envelope{ Date: *m.ReceivedAt, Subject: m.Subject, From: translateAddrList(m.From), ReplyTo: translateAddrList(m.ReplyTo), To: translateAddrList(m.To), Cc: translateAddrList(m.CC), Bcc: translateAddrList(m.BCC), MessageId: firstString(m.MessageID), InReplyTo: firstString(m.InReplyTo), } labels := make([]string, 0, len(m.MailboxIDs)) for id := range m.MailboxIDs { if dir, ok := w.mbox2dir[id]; ok { labels = append(labels, dir) } } sort.Strings(labels) return &models.MessageInfo{ Envelope: env, Flags: keywordsToFlags(m.Keywords), Uid: w.uidStore.GetOrInsert(string(m.ID)), BodyStructure: translateBodyStructure(m.BodyStructure), RFC822Headers: translateJMAPHeader(m.Headers), Refs: m.References, Labels: labels, Size: uint32(m.Size), InternalDate: *m.ReceivedAt, } } func translateJMAPHeader(headers []*email.Header) *msgmail.Header { hdr := new(msgmail.Header) for _, h := range headers { raw := fmt.Sprintf("%s:%s\r\n", h.Name, h.Value) hdr.AddRaw([]byte(raw)) } return hdr } func flagsToKeywords(flags models.Flags) map[string]bool { kw := make(map[string]bool) if flags.Has(models.SeenFlag) { kw["$seen"] = true } if flags.Has(models.AnsweredFlag) { kw["$answered"] = true } if flags.Has(models.FlaggedFlag) { kw["$flagged"] = true } if flags.Has(models.DraftFlag) { kw["$draft"] = true } return kw } func keywordsToFlags(kw map[string]bool) models.Flags { var f models.Flags for k, v := range kw { if v { switch k { case "$seen": f |= models.SeenFlag case "$answered": f |= models.AnsweredFlag case "$flagged": f |= models.FlaggedFlag case "$draft": f |= models.DraftFlag } } } return f } func (w *JMAPWorker) MailboxPath(mbox *mailbox.Mailbox) string { if mbox == nil { return "" } if mbox.ParentID == "" { return mbox.Name } parent, err := w.cache.GetMailbox(mbox.ParentID) if err != nil { w.w.Warnf("MailboxPath/GetMailbox: %s", err) return mbox.Name } return w.MailboxPath(parent) + "/" + mbox.Name } var jmapRole2aerc = map[mailbox.Role]models.Role{ mailbox.RoleAll: models.AllRole, mailbox.RoleArchive: models.ArchiveRole, mailbox.RoleDrafts: models.DraftsRole, mailbox.RoleInbox: models.InboxRole, mailbox.RoleJunk: models.JunkRole, mailbox.RoleSent: models.SentRole, mailbox.RoleTrash: models.TrashRole, } func firstString(s []string) string { if len(s) == 0 { return "" } return s[0] } func translateAddrList(addrs []*mail.Address) []*msgmail.Address { res := make([]*msgmail.Address, 0, len(addrs)) for _, a := range addrs { res = append(res, &msgmail.Address{Name: a.Name, Address: a.Email}) } return res } func translateBodyStructure(part *email.BodyPart) *models.BodyStructure { bs := &models.BodyStructure{ Description: part.Name, Encoding: part.Charset, Params: map[string]string{ "name": part.Name, "charset": part.Charset, }, Disposition: part.Disposition, DispositionParams: map[string]string{ "filename": part.Name, }, } bs.MIMEType, bs.MIMESubType, _ = strings.Cut(part.Type, "/") for _, sub := range part.SubParts { bs.Parts = append(bs.Parts, translateBodyStructure(sub)) } return bs } func wrapSetError(err *jmap.SetError) error { var s string if err.Description != nil { s = *err.Description } else { s = err.Type if err.Properties != nil { s += fmt.Sprintf(" %v", *err.Properties) } if s == "invalidProperties: [mailboxIds]" { s = "a message must belong to one or more mailboxes" } } return errors.New(s) } func wrapMethodError(err *jmap.MethodError) error { var s string if err.Description != nil { s = *err.Description } else { s = err.Type } return errors.New(s) }