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}