1package view
2
3import (
4 "fmt"
5 "io"
6 "mime/quotedprintable"
7 "strings"
8
9 "github.com/PuerkitoBio/goquery"
10)
11
12// hyperlink formats a string as a terminal-clickable hyperlink.
13func hyperlink(url, text string) string {
14 if text == "" {
15 text = url
16 }
17 return fmt.Sprintf("\x1b]8;;%s\x07%s\x1b]8;;\x07", url, text)
18}
19
20func decodeQuotedPrintable(s string) (string, error) {
21 reader := quotedprintable.NewReader(strings.NewReader(s))
22 body, err := io.ReadAll(reader)
23 if err != nil {
24 return "", err
25 }
26 return string(body), nil
27}
28
29// ProcessBody takes a raw email body, decodes it, and formats it as plain
30// text with terminal hyperlinks.
31func ProcessBody(rawBody string) (string, error) {
32 decodedBody, err := decodeQuotedPrintable(rawBody)
33 if err != nil {
34 // If decoding fails, fallback to the raw body
35 decodedBody = rawBody
36 }
37
38 doc, err := goquery.NewDocumentFromReader(strings.NewReader(decodedBody))
39 if err != nil {
40 return "", fmt.Errorf("could not parse email body: %w", err)
41 }
42
43 // Remove style and script tags to clean up the view
44 doc.Find("style, script").Remove()
45
46 // Add newlines after block elements for better spacing
47 doc.Find("p, div, h1, h2, h3, h4, h5, h6").Each(func(i int, s *goquery.Selection) {
48 s.After("\n\n")
49 })
50
51 // Replace <br> tags with newlines
52 doc.Find("br").Each(func(i int, s *goquery.Selection) {
53 s.ReplaceWithHtml("\n")
54 })
55
56 // Format links into terminal hyperlinks
57 doc.Find("a").Each(func(i int, s *goquery.Selection) {
58 href, exists := s.Attr("href")
59 if !exists {
60 return
61 }
62 s.ReplaceWithHtml(hyperlink(href, s.Text()))
63 })
64
65 // Return the document's text content, which now includes our formatting
66 return doc.Text(), nil
67}