fetcher.go

  1package fetcher
  2
  3import (
  4	"crypto/tls"
  5	"fmt"
  6	"log"
  7	"time"
  8
  9	"github.com/andrinoff/email-cli/config"
 10	"github.com/emersion/go-imap/v2"
 11	"github.com/emersion/go-imap/v2/imapclient"
 12)
 13
 14// Email struct holds the essential information for a single email.
 15type Email struct {
 16	From    string
 17	Subject string
 18	Body    string
 19	Date    time.Time
 20}
 21
 22// FetchEmails connects to the email provider's IMAP server and retrieves the latest emails.
 23func FetchEmails(cfg *config.Config) ([]Email, error) {
 24	var imapHost, serverName string
 25	switch cfg.ServiceProvider {
 26	case "gmail":
 27		imapHost = "imap.gmail.com:993"
 28		serverName = "imap.gmail.com"
 29	case "icloud":
 30		imapHost = "imap.mail.me.com:993"
 31		serverName = "imap.mail.me.com"
 32	default:
 33		return nil, fmt.Errorf("unsupported service provider: %s", cfg.ServiceProvider)
 34	}
 35
 36	options := imapclient.Options{
 37		TLSConfig: &tls.Config{ServerName: serverName},
 38	}
 39	c, err := imapclient.DialTLS(imapHost, &options)
 40	if err != nil {
 41		return nil, fmt.Errorf("failed to dial IMAP server: %w", err)
 42	}
 43	defer c.Close()
 44
 45	if err := c.Login(cfg.Email, cfg.Password).Wait(); err != nil {
 46		return nil, fmt.Errorf("failed to login: %w", err)
 47	}
 48
 49	selectData, err := c.Select("INBOX", nil).Wait()
 50	if err != nil {
 51		return nil, fmt.Errorf("failed to select INBOX: %w", err)
 52	}
 53	numMessages := selectData.NumMessages
 54
 55	if numMessages == 0 {
 56		return []Email{}, nil // No messages
 57	}
 58
 59	start := uint32(1)
 60	if numMessages > 10 {
 61		start = numMessages - 9
 62	}
 63	seqSet := imap.SeqSetNum(start, numMessages)
 64
 65	fetchOptions := &imap.FetchOptions{
 66		Envelope: true,
 67		BodySection: []*imap.FetchItemBodySection{
 68			{Specifier: imap.PartSpecifierText},
 69		},
 70	}
 71
 72	cmd := c.Fetch(seqSet, fetchOptions)
 73
 74	var emails []Email
 75	for {
 76		msg := cmd.Next()
 77		if msg == nil {
 78			break
 79		}
 80
 81		buf, err := msg.Collect()
 82		if err != nil {
 83			log.Printf("failed to collect message data: %v", err)
 84			continue
 85		}
 86		
 87		var from, subject, body string
 88		var date time.Time
 89
 90		if buf.Envelope != nil {
 91			if len(buf.Envelope.From) > 0 {
 92				from = buf.Envelope.From[0].Addr()
 93			}
 94			subject = buf.Envelope.Subject
 95			date = buf.Envelope.Date
 96		}
 97		
 98		// Corrected: FindBodySection returns []byte, not io.Reader
 99		if bodySection := buf.FindBodySection(&imap.FetchItemBodySection{Specifier: imap.PartSpecifierText}); bodySection != nil {
100			body = string(bodySection) // Directly convert []byte to string
101		}
102
103		emails = append(emails, Email{
104			From:    from,
105			Subject: subject,
106			Body:    body,
107			Date:    date,
108		})
109	}
110
111	if err := cmd.Close(); err != nil {
112		return nil, fmt.Errorf("FETCH command failed: %w", err)
113	}
114
115	// Reverse the slice to show the newest emails first
116	for i, j := 0, len(emails)-1; i < j; i, j = i+1, j-1 {
117		emails[i], emails[j] = emails[j], emails[i]
118	}
119
120	return emails, nil
121}