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) DeleteEmails(_ context.Context, folder string, uids []uint32) error {
67 return fetcher.DeleteEmailsFromMailbox(p.account, folder, uids)
68}
69
70func (p *Provider) ArchiveEmails(_ context.Context, folder string, uids []uint32) error {
71 return fetcher.ArchiveEmailsFromMailbox(p.account, folder, uids)
72}
73
74func (p *Provider) MoveEmails(_ context.Context, uids []uint32, srcFolder, dstFolder string) error {
75 return fetcher.MoveEmailsToFolder(p.account, uids, srcFolder, dstFolder)
76}
77
78func (p *Provider) SendEmail(_ context.Context, msg *backend.OutgoingEmail) error {
79 return sender.SendEmail(
80 p.account, msg.To, msg.Cc, msg.Bcc,
81 msg.Subject, msg.PlainBody, msg.HTMLBody,
82 msg.Images, msg.Attachments,
83 msg.InReplyTo, msg.References,
84 msg.SignSMIME, msg.EncryptSMIME,
85 msg.SignPGP, msg.EncryptPGP,
86 )
87}
88
89func (p *Provider) FetchFolders(_ context.Context) ([]backend.Folder, error) {
90 folders, err := fetcher.FetchFolders(p.account)
91 if err != nil {
92 return nil, err
93 }
94 return toBackendFolders(folders), nil
95}
96
97func (p *Provider) Watch(_ context.Context, _ string) (<-chan backend.NotifyEvent, func(), error) {
98 // IMAP IDLE is handled by the existing IdleWatcher in main.go
99 return nil, nil, backend.ErrNotSupported
100}
101
102func (p *Provider) Close() error {
103 return nil
104}
105
106// Verify interface compliance at compile time.
107var _ backend.Provider = (*Provider)(nil)
108
109// Conversion helpers
110
111func toBackendEmails(emails []fetcher.Email) []backend.Email {
112 result := make([]backend.Email, len(emails))
113 for i, e := range emails {
114 result[i] = backend.Email{
115 UID: e.UID,
116 From: e.From,
117 To: e.To,
118 Subject: e.Subject,
119 Body: e.Body,
120 Date: e.Date,
121 IsRead: e.IsRead,
122 MessageID: e.MessageID,
123 References: e.References,
124 Attachments: toBackendAttachments(e.Attachments),
125 AccountID: e.AccountID,
126 }
127 }
128 return result
129}
130
131func toBackendAttachments(atts []fetcher.Attachment) []backend.Attachment {
132 result := make([]backend.Attachment, len(atts))
133 for i, a := range atts {
134 result[i] = backend.Attachment{
135 Filename: a.Filename,
136 PartID: a.PartID,
137 Data: a.Data,
138 Encoding: a.Encoding,
139 MIMEType: a.MIMEType,
140 ContentID: a.ContentID,
141 Inline: a.Inline,
142 IsSMIMESignature: a.IsSMIMESignature,
143 SMIMEVerified: a.SMIMEVerified,
144 IsSMIMEEncrypted: a.IsSMIMEEncrypted,
145 }
146 }
147 return result
148}
149
150func toBackendFolders(folders []fetcher.Folder) []backend.Folder {
151 result := make([]backend.Folder, len(folders))
152 for i, f := range folders {
153 result[i] = backend.Folder{
154 Name: f.Name,
155 Delimiter: f.Delimiter,
156 Attributes: f.Attributes,
157 }
158 }
159 return result
160}