234
package main
import (
"bytes"
"encoding/base64"
"fmt"
"github.com/DusanKasan/parsemail"
"github.com/andybalholm/brotli"
"golang.org/x/crypto/ssh/terminal"
"golang.org/x/net/html"
"io"
"net/mail"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
)
func GetTSize() (int, int) {
w, h, err := terminal.GetSize(int(os.Stdin.Fd()))
if err != nil {
return 80, 24
}
return w, h
}
func TimeStr(s string) string {
t, err := time.Parse(TimeFormat, s)
if err != nil {
return s
}
n := time.Now().Local()
return strings.ReplaceAll(func() string {
if t.Year() != n.Year() {
return t.Format("Jan 02th 2006")
}
d := time.Since(t)
if d.Hours() > 24*6 {
return t.Format("Jan 02th")
}
if d.Hours() > 24 {
return t.Format("Monday 15:04")
}
if d.Hours() > 1 {
return fmt.Sprintf("%d h ago", int(d.Hours()))
}
return fmt.Sprintf("%d m ago", int(d.Minutes()))
}(), "th", (func() string {
if t.Day()%9 < 1 || t.Day()%9 > 3 {
return "th"
}
return []string{"st", "nd", "rd"}[t.Day()%9-1]
})())
}
func displayAddress(a string) string {
addr, err := mail.ParseAddress(a)
if err != nil {
return a
}
if addr.Name != "" {
return addr.Name
}
return addr.Address
}
func UnBr(buf *bytes.Buffer) error {
data := buf.Bytes()
buf.Reset()
reader := brotli.NewReader(bytes.NewReader(data))
_, err := buf.ReadFrom(reader)
return err
}
func Show(b *bytes.Buffer) error {
e, err := parsemail.Parse(b)
if err != nil {
return err
}
var s string
var ishtml bool
if e.HTMLBody != "" {
s = e.HTMLBody
ishtml = true
} else {
s = e.TextBody
ishtml = false
}
var bb []byte
if bb, err = base64.StdEncoding.DecodeString(s); err == nil {
s = string(bb)
}
if ishtml {
s = RenderHTML(s)
}
b.Reset()
b.WriteString(s)
return nil
}
func node(w io.Writer, n *html.Node) {
switch n.Type {
case html.ElementNode:
switch n.Data {
case "script", "style", "head", "img":
return
case "b", "strong":
fmt.Fprint(w, "\033[1m") // Bold
case "i":
fmt.Fprint(w, "\033[2m") // Italic
}
for c := n.FirstChild; c != nil; c = c.NextSibling {
node(w, c)
}
// Reset styles after the element
switch n.Data {
case "b", "strong", "i", "em":
fmt.Fprint(w, "\033[0m")
}
case html.TextNode:
fmt.Fprint(w, n.Data, " ")
default:
for c := n.FirstChild; c != nil; c = c.NextSibling {
node(w, c)
}
}
}
func RenderHTML(htmlContent string) string {
doc, err := html.Parse(bytes.NewBufferString(htmlContent))
if err != nil {
panic(err)
}
var buff bytes.Buffer
node(&buff, doc)
return buff.String()
}
func (em EmailMeta) Display() string {
s := fmt.Sprintf("%13.13s | ", TimeStr(em.Date))
s += fmt.Sprintf("%40.40s | ", displayAddress(em.From))
s += fmt.Sprintf("%s", em.Subject)
return s
}
func DisplayRows(metas []EmailMeta, page int, h int) {
fmt.Println("Page: ", page)
fmt.Printf("id:\t%13.13s | %40.40s | %s\n", "Date", "From", "Subject")
page *= h
for i := page; i < len(metas) && i < page+h; i++ {
fmt.Printf("%d:\t%s\n", i-page, metas[i].Display())
}
}
func OpenMail(metas []EmailMeta, page int, h int, s string) error {
n := 0
for _, r := range s {
if r < '0' || r > '9' {
break
}
n = n*10 + int(r-'0')
}
if !strings.HasPrefix(s, fmt.Sprintf("%d", n)) {
return fmt.Errorf("invalid number")
}
n += page * h
if n >= len(metas) || n < 0 {
return fmt.Errorf("invalid number")
}
id := metas[n].Id
b, err := os.ReadFile(filepath.Join(SAVEPATH, id+".br"))
if err != nil {
return err
}
var email bytes.Buffer
email.Write(b)
if err = UnBr(&email); err != nil {
return err
}
if err = Show(&email); err != nil {
return err
}
cmd := exec.Command("less")
cmd.Stdin = &email
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
}
func CLI(metas *[]EmailMeta) {
// use alternate screen
fmt.Print("\033[?1049h")
defer fmt.Print("\033[?1049l")
page := 0
var s string
_, h := GetTSize()
h -= 5
if h > 20 {
h = 20
} else if h < 5 {
h = 5
}
for {
// clear screen and move cursor to top
fmt.Print("\033[2J\033[H\r")
if page < 0 {
page = 0
}
DisplayRows(*metas, page, h)
fmt.Println("n: next, p: previous, q: quit, (id): open")
fmt.Print("(n/p/q/id): ")
fmt.Scanln(&s)
switch s {
case "n":
page++
case "p":
page--
case "q":
return
case "":
continue
default: // open mail
if err := OpenMail(*metas, page, h, s); err != nil {
fmt.Println(err)
}
}
}
}