sophuwu.site > mailboxxer   
              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)
			}
		}
	}
}