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