1// Package imap implements the backend.Provider interface by delegating
2// to the existing fetcher and sender packages.
3package imap
4
5import (
6 "context"
7
8 "github.com/floatpane/matcha/backend"
9 "github.com/floatpane/matcha/config"
10 "github.com/floatpane/matcha/fetcher"
11 "github.com/floatpane/matcha/sender"
12)
13
14func init() {
15 backend.RegisterBackend("imap", func(account *config.Account) (backend.Provider, error) {
16 return New(account)
17 })
18}
19
20// Provider wraps the existing fetcher/sender packages behind the backend.Provider interface.
21type Provider struct {
22 account *config.Account
23}
24
25// New creates a new IMAP provider.
26func New(account *config.Account) (*Provider, error) {
27 return &Provider{account: account}, nil
28}
29
30func (p *Provider) FetchEmails(_ context.Context, folder string, limit, offset uint32) ([]backend.Email, error) {
31 emails, err := fetcher.FetchMailboxEmails(p.account, folder, limit, offset)
32 if err != nil {
33 return nil, err
34 }
35 return toBackendEmails(emails), nil
36}
37
38func (p *Provider) FetchEmailBody(_ context.Context, folder string, uid uint32) (string, []backend.Attachment, error) {
39 body, atts, err := fetcher.FetchEmailBodyFromMailbox(p.account, folder, uid)
40 if err != nil {
41 return "", nil, err
42 }
43 return body, toBackendAttachments(atts), nil
44}
45
46func (p *Provider) FetchAttachment(_ context.Context, folder string, uid uint32, partID, encoding string) ([]byte, error) {
47 return fetcher.FetchAttachmentFromMailbox(p.account, folder, uid, partID, encoding)
48}
49
50func (p *Provider) MarkAsRead(_ context.Context, folder string, uid uint32) error {
51 return fetcher.MarkEmailAsReadInMailbox(p.account, folder, uid)
52}
53
54func (p *Provider) DeleteEmail(_ context.Context, folder string, uid uint32) error {
55 return fetcher.DeleteEmailFromMailbox(p.account, folder, uid)
56}
57
58func (p *Provider) ArchiveEmail(_ context.Context, folder string, uid uint32) error {
59 return fetcher.ArchiveEmailFromMailbox(p.account, folder, uid)
60}
61
62func (p *Provider) MoveEmail(_ context.Context, uid uint32, srcFolder, dstFolder string) error {
63 return fetcher.MoveEmailToFolder(p.account, uid, srcFolder, dstFolder)
64}
65
66func (p *Provider) SendEmail(_ context.Context, msg *backend.OutgoingEmail) error {
67 return sender.SendEmail(
68 p.account, msg.To, msg.Cc, msg.Bcc,
69 msg.Subject, msg.PlainBody, msg.HTMLBody,
70 msg.Images, msg.Attachments,
71 msg.InReplyTo, msg.References,
72 msg.SignSMIME, msg.EncryptSMIME,
73 msg.SignPGP, msg.EncryptPGP,
74 )
75}
76
77func (p *Provider) FetchFolders(_ context.Context) ([]backend.Folder, error) {
78 folders, err := fetcher.FetchFolders(p.account)
79 if err != nil {
80 return nil, err
81 }
82 return toBackendFolders(folders), nil
83}
84
85func (p *Provider) Watch(_ context.Context, _ string) (<-chan backend.NotifyEvent, func(), error) {
86 // IMAP IDLE is handled by the existing IdleWatcher in main.go
87 return nil, nil, backend.ErrNotSupported
88}
89
90func (p *Provider) Close() error {
91 return nil
92}
93
94// Verify interface compliance at compile time.
95var _ backend.Provider = (*Provider)(nil)
96
97// Conversion helpers
98
99func toBackendEmails(emails []fetcher.Email) []backend.Email {
100 result := make([]backend.Email, len(emails))
101 for i, e := range emails {
102 result[i] = backend.Email{
103 UID: e.UID,
104 From: e.From,
105 To: e.To,
106 Subject: e.Subject,
107 Body: e.Body,
108 Date: e.Date,
109 IsRead: e.IsRead,
110 MessageID: e.MessageID,
111 References: e.References,
112 Attachments: toBackendAttachments(e.Attachments),
113 AccountID: e.AccountID,
114 }
115 }
116 return result
117}
118
119func toBackendAttachments(atts []fetcher.Attachment) []backend.Attachment {
120 result := make([]backend.Attachment, len(atts))
121 for i, a := range atts {
122 result[i] = backend.Attachment{
123 Filename: a.Filename,
124 PartID: a.PartID,
125 Data: a.Data,
126 Encoding: a.Encoding,
127 MIMEType: a.MIMEType,
128 ContentID: a.ContentID,
129 Inline: a.Inline,
130 IsSMIMESignature: a.IsSMIMESignature,
131 SMIMEVerified: a.SMIMEVerified,
132 IsSMIMEEncrypted: a.IsSMIMEEncrypted,
133 }
134 }
135 return result
136}
137
138func toBackendFolders(folders []fetcher.Folder) []backend.Folder {
139 result := make([]backend.Folder, len(folders))
140 for i, f := range folders {
141 result[i] = backend.Folder{
142 Name: f.Name,
143 Delimiter: f.Delimiter,
144 Attributes: f.Attributes,
145 }
146 }
147 return result
148}