summaryrefslogtreecommitdiff
path: root/utils/email/messages.go
diff options
context:
space:
mode:
Diffstat (limited to 'utils/email/messages.go')
-rw-r--r--utils/email/messages.go230
1 files changed, 230 insertions, 0 deletions
diff --git a/utils/email/messages.go b/utils/email/messages.go
new file mode 100644
index 0000000..cedbec5
--- /dev/null
+++ b/utils/email/messages.go
@@ -0,0 +1,230 @@
+package email
+
+import (
+ "fmt"
+ "io"
+ "lain/types"
+
+ "github.com/emersion/go-imap"
+ "github.com/emersion/go-message/mail"
+)
+
+func SelectFolder(client *types.EmailClient, folderName string) (*imap.MailboxStatus, error) {
+ mbox, err := client.Select(folderName, false)
+ if err != nil {
+ return nil, fmt.Errorf("failed to select folder: %w", err)
+ }
+ return mbox, nil
+}
+
+func FetchMessages(client *types.EmailClient, folderName string, limit uint32) ([]*types.EmailMessage, error) {
+ mbox, err := SelectFolder(client, folderName)
+ if err != nil {
+ return nil, err
+ }
+
+ if mbox.Messages == 0 {
+ return []*types.EmailMessage{}, nil
+ }
+
+ from := uint32(1)
+ to := mbox.Messages
+ if mbox.Messages > limit {
+ from = mbox.Messages - limit + 1
+ }
+
+ seqset := new(imap.SeqSet)
+ seqset.AddRange(from, to)
+
+ messages := make(chan *imap.Message, 10)
+ done := make(chan error, 1)
+
+ section := &imap.BodySectionName{}
+ items := []imap.FetchItem{imap.FetchEnvelope, imap.FetchFlags, imap.FetchUid, imap.FetchRFC822Size, section.FetchItem()}
+
+ go func() {
+ done <- client.Fetch(seqset, items, messages)
+ }()
+
+ var result []*types.EmailMessage
+ for msg := range messages {
+ if msg == nil {
+ continue
+ }
+
+ emailMsg, err := parseMessage(msg)
+ if err != nil {
+ continue
+ }
+
+ result = append(result, emailMsg)
+ }
+
+ if err := <-done; err != nil {
+ return nil, fmt.Errorf("failed to fetch messages: %w", err)
+ }
+
+ return result, nil
+}
+
+func parseMessage(msg *imap.Message) (*types.EmailMessage, error) {
+ if msg.Envelope == nil {
+ return nil, fmt.Errorf("message envelope is nil")
+ }
+
+ section := &imap.BodySectionName{}
+ bodyReader := msg.GetBody(section)
+ if bodyReader == nil {
+ return nil, fmt.Errorf("message body is nil")
+ }
+
+ mr, err := mail.CreateReader(bodyReader)
+ if err != nil {
+ return nil, fmt.Errorf("failed to create mail reader: %w", err)
+ }
+
+ var bodyText, bodyHTML string
+ var attachments []types.EmailAttachment
+
+ for {
+ part, err := mr.NextPart()
+ if err == io.EOF {
+ break
+ }
+ if err != nil {
+ continue
+ }
+
+ switch h := part.Header.(type) {
+ case *mail.InlineHeader:
+ contentType, _, _ := h.ContentType()
+ body, _ := io.ReadAll(part.Body)
+
+ if contentType == "text/plain" {
+ bodyText = string(body)
+ } else if contentType == "text/html" {
+ bodyHTML = string(body)
+ }
+
+ case *mail.AttachmentHeader:
+ filename, _ := h.Filename()
+ contentType, _, _ := h.ContentType()
+ data, _ := io.ReadAll(part.Body)
+
+ attachments = append(attachments, types.EmailAttachment{
+ Filename: filename,
+ ContentType: contentType,
+ Data: data,
+ })
+ }
+ }
+
+ var fromAddr, fromName string
+ if len(msg.Envelope.From) > 0 {
+ fromAddr = msg.Envelope.From[0].MailboxName + "@" + msg.Envelope.From[0].HostName
+ fromName = msg.Envelope.From[0].PersonalName
+ }
+
+ var toList []string
+ for _, addr := range msg.Envelope.To {
+ toList = append(toList, addr.MailboxName+"@"+addr.HostName)
+ }
+
+ var ccList []string
+ for _, addr := range msg.Envelope.Cc {
+ ccList = append(ccList, addr.MailboxName+"@"+addr.HostName)
+ }
+
+ var bccList []string
+ for _, addr := range msg.Envelope.Bcc {
+ bccList = append(bccList, addr.MailboxName+"@"+addr.HostName)
+ }
+
+ var replyToList []string
+ for _, addr := range msg.Envelope.ReplyTo {
+ replyToList = append(replyToList, addr.MailboxName+"@"+addr.HostName)
+ }
+
+ isRead := false
+ isFlagged := false
+ isAnswered := false
+ isDraft := false
+
+ for _, flag := range msg.Flags {
+ switch flag {
+ case imap.SeenFlag:
+ isRead = true
+ case imap.FlaggedFlag:
+ isFlagged = true
+ case imap.AnsweredFlag:
+ isAnswered = true
+ case imap.DraftFlag:
+ isDraft = true
+ }
+ }
+
+ return &types.EmailMessage{
+ UID: msg.Uid,
+ MessageID: msg.Envelope.MessageId,
+ From: fromAddr,
+ FromName: fromName,
+ To: toList,
+ CC: ccList,
+ BCC: bccList,
+ ReplyTo: replyToList,
+ Subject: msg.Envelope.Subject,
+ Date: msg.Envelope.Date,
+ BodyText: bodyText,
+ BodyHTML: bodyHTML,
+ Size: msg.Size,
+ InReplyTo: msg.Envelope.InReplyTo,
+ IsRead: isRead,
+ IsFlagged: isFlagged,
+ IsAnswered: isAnswered,
+ IsDraft: isDraft,
+ HasAttachment: len(attachments) > 0,
+ Attachments: attachments,
+ }, nil
+}
+
+func MarkAsRead(client *types.EmailClient, folderName string, uid uint32) error {
+ if _, err := SelectFolder(client, folderName); err != nil {
+ return err
+ }
+
+ seqSet := new(imap.SeqSet)
+ seqSet.AddNum(uid)
+
+ item := imap.FormatFlagsOp(imap.AddFlags, true)
+ flags := []interface{}{imap.SeenFlag}
+
+ if err := client.UidStore(seqSet, item, flags, nil); err != nil {
+ return fmt.Errorf("failed to mark as read: %w", err)
+ }
+
+ return nil
+}
+
+func ToggleFlag(client *types.EmailClient, folderName string, uid uint32, isFlagged bool) error {
+ if _, err := SelectFolder(client, folderName); err != nil {
+ return err
+ }
+
+ seqSet := new(imap.SeqSet)
+ seqSet.AddNum(uid)
+
+ var item imap.StoreItem
+ if isFlagged {
+ item = imap.FormatFlagsOp(imap.RemoveFlags, true)
+ } else {
+ item = imap.FormatFlagsOp(imap.AddFlags, true)
+ }
+
+ flags := []interface{}{imap.FlaggedFlag}
+
+ if err := client.UidStore(seqSet, item, flags, nil); err != nil {
+ return fmt.Errorf("failed to toggle flag: %w", err)
+ }
+
+ return nil
+}