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) 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}