imap.go

  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}