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 )
74}
75
76func (p *Provider) FetchFolders(_ context.Context) ([]backend.Folder, error) {
77 folders, err := fetcher.FetchFolders(p.account)
78 if err != nil {
79 return nil, err
80 }
81 return toBackendFolders(folders), nil
82}
83
84func (p *Provider) Watch(_ context.Context, _ string) (<-chan backend.NotifyEvent, func(), error) {
85 // IMAP IDLE is handled by the existing IdleWatcher in main.go
86 return nil, nil, backend.ErrNotSupported
87}
88
89func (p *Provider) Close() error {
90 return nil
91}
92
93// Verify interface compliance at compile time.
94var _ backend.Provider = (*Provider)(nil)
95
96// Conversion helpers
97
98func toBackendEmails(emails []fetcher.Email) []backend.Email {
99 result := make([]backend.Email, len(emails))
100 for i, e := range emails {
101 result[i] = backend.Email{
102 UID: e.UID,
103 From: e.From,
104 To: e.To,
105 Subject: e.Subject,
106 Body: e.Body,
107 Date: e.Date,
108 IsRead: e.IsRead,
109 MessageID: e.MessageID,
110 References: e.References,
111 Attachments: toBackendAttachments(e.Attachments),
112 AccountID: e.AccountID,
113 }
114 }
115 return result
116}
117
118func toBackendAttachments(atts []fetcher.Attachment) []backend.Attachment {
119 result := make([]backend.Attachment, len(atts))
120 for i, a := range atts {
121 result[i] = backend.Attachment{
122 Filename: a.Filename,
123 PartID: a.PartID,
124 Data: a.Data,
125 Encoding: a.Encoding,
126 MIMEType: a.MIMEType,
127 ContentID: a.ContentID,
128 Inline: a.Inline,
129 IsSMIMESignature: a.IsSMIMESignature,
130 SMIMEVerified: a.SMIMEVerified,
131 IsSMIMEEncrypted: a.IsSMIMEEncrypted,
132 }
133 }
134 return result
135}
136
137func toBackendFolders(folders []fetcher.Folder) []backend.Folder {
138 result := make([]backend.Folder, len(folders))
139 for i, f := range folders {
140 result[i] = backend.Folder{
141 Name: f.Name,
142 Delimiter: f.Delimiter,
143 Attributes: f.Attributes,
144 }
145 }
146 return result
147}