1package fetcher
2
3import (
4 "fmt"
5 "io"
6 "io/ioutil"
7 "log"
8 "mime"
9 "strings"
10 "time"
11
12 "github.com/andrinoff/email-cli/config"
13 "github.com/emersion/go-imap"
14 "github.com/emersion/go-imap/client"
15 "github.com/emersion/go-message/mail"
16 "golang.org/x/text/encoding/ianaindex"
17 "golang.org/x/text/transform"
18)
19
20type Email struct {
21 From string
22 To []string
23 Subject string
24 Body string
25 Date time.Time
26}
27
28// ... (decodePart and decodeHeader functions remain the same)
29func decodePart(reader io.Reader, header mail.PartHeader) (string, error) {
30 mediaType, params, err := mime.ParseMediaType(header.Get("Content-Type"))
31 if err != nil {
32 body, _ := ioutil.ReadAll(reader)
33 return string(body), nil
34 }
35
36 charset := "utf-8" // Default charset
37 if params["charset"] != "" {
38 charset = strings.ToLower(params["charset"])
39 }
40
41 encoding, err := ianaindex.IANA.Encoding(charset)
42 if err != nil || encoding == nil {
43 encoding, _ = ianaindex.IANA.Encoding("utf-8")
44 }
45
46 transformReader := transform.NewReader(reader, encoding.NewDecoder())
47 decodedBody, err := ioutil.ReadAll(transformReader)
48 if err != nil {
49 return "", err
50 }
51
52 if strings.HasPrefix(mediaType, "multipart/") {
53 return "[This is a multipart message]", nil
54 }
55
56 return string(decodedBody), nil
57}
58
59func decodeHeader(header string) string {
60 dec := new(mime.WordDecoder)
61 dec.CharsetReader = func(charset string, input io.Reader) (io.Reader, error) {
62 encoding, err := ianaindex.IANA.Encoding(charset)
63 if err != nil {
64 return nil, err
65 }
66 return transform.NewReader(input, encoding.NewDecoder()), nil
67 }
68 decoded, err := dec.DecodeHeader(header)
69 if err != nil {
70 return header
71 }
72 return decoded
73}
74
75// FetchEmails now supports pagination with a limit and offset.
76func FetchEmails(cfg *config.Config, limit, offset uint32) ([]Email, error) {
77 var imapServer string
78 var imapPort int
79
80 switch cfg.ServiceProvider {
81 case "gmail":
82 imapServer = "imap.gmail.com"
83 imapPort = 993
84 case "icloud":
85 imapServer = "imap.mail.me.com"
86 imapPort = 993
87 default:
88 return nil, fmt.Errorf("unsupported or missing service_provider in config.json: %s", cfg.ServiceProvider)
89 }
90
91 addr := fmt.Sprintf("%s:%d", imapServer, imapPort)
92 c, err := client.DialTLS(addr, nil)
93 if err != nil {
94 return nil, err
95 }
96 log.Println("Connected")
97 defer c.Logout()
98
99 if err := c.Login(cfg.Email, cfg.Password); err != nil {
100 return nil, err
101 }
102 log.Println("Logged in")
103
104 mbox, err := c.Select("INBOX", false)
105 if err != nil {
106 return nil, err
107 }
108
109 // Handle pagination logic
110 if mbox.Messages == 0 {
111 return []Email{}, nil
112 }
113
114 to := mbox.Messages - offset
115 from := uint32(1)
116 if to > limit {
117 from = to - limit + 1
118 }
119
120 if to < 1 {
121 return []Email{}, nil // No more messages to fetch
122 }
123
124 seqset := new(imap.SeqSet)
125 seqset.AddRange(from, to)
126
127 messages := make(chan *imap.Message, limit)
128 done := make(chan error, 1)
129 go func() {
130 done <- c.Fetch(seqset, []imap.FetchItem{imap.FetchEnvelope, imap.FetchItem("BODY[]")}, messages)
131 }()
132
133 var emails []Email
134 for msg := range messages {
135 if msg == nil {
136 continue
137 }
138
139 bodyLiteral := msg.GetBody(&imap.BodySectionName{})
140 if bodyLiteral == nil {
141 log.Println("Could not get message body")
142 continue
143 }
144
145 mr, err := mail.CreateReader(bodyLiteral)
146 if err != nil {
147 log.Printf("Error creating mail reader: %v", err)
148 continue
149 }
150
151 header := mr.Header
152 fromAddrs, _ := header.AddressList("From")
153 toAddrs, _ := header.AddressList("To")
154 subject := decodeHeader(header.Get("Subject"))
155 date, _ := header.Date()
156
157 var fromAddr string
158 if len(fromAddrs) > 0 {
159 fromAddr = fromAddrs[0].Address
160 }
161
162 var toAddrList []string
163 for _, addr := range toAddrs {
164 toAddrList = append(toAddrList, addr.Address)
165 }
166
167 var body string
168 for {
169 p, err := mr.NextPart()
170 if err == io.EOF {
171 break
172 } else if err != nil {
173 log.Printf("Error getting next part: %v", err)
174 break
175 }
176
177 mediaType, _, _ := mime.ParseMediaType(p.Header.Get("Content-Type"))
178 if mediaType == "text/plain" || mediaType == "text/html" {
179 decodedPart, decodeErr := decodePart(p.Body, p.Header)
180 if decodeErr == nil {
181 body = decodedPart
182 break
183 }
184 }
185 }
186
187 emails = append(emails, Email{
188 From: fromAddr,
189 To: toAddrList,
190 Subject: subject,
191 Body: body,
192 Date: date,
193 })
194 }
195
196 if err := <-done; err != nil {
197 return nil, err
198 }
199
200 for i, j := 0, len(emails)-1; i < j; i, j = i+1, j-1 {
201 emails[i], emails[j] = emails[j], emails[i]
202 }
203
204 return emails, nil
205}