ref: 0b98045c088f379622fdb3b00c0f3c5c88ed4134
dir: /renderer.go/
package main
import (
"bytes"
"fmt"
"strings"
"github.com/k3a/html2text"
mastodon "codeberg.org/penny64/hellclient-go-mastodon"
"golang.org/x/net/html"
)
type StatusFormatter struct {
status *mastodon.Status
postContext *postref
notif *mastodon.Notification
prefs *Hellprefs
// Index for posts that aren't using the ref in postref
localindex string
}
// Returns the rendered content of a status's rt
func (st *StatusFormatter) reblogContent() string {
currentpost := st.status
defer func() { st.status = currentpost }()
return st.statusContent(st.status.Reblog)
}
// Returns the rendered content of a status
func (st *StatusFormatter) statusContent(status *mastodon.Status) string {
renderedPost, plaintexts := st.postContext.renderStatus(status.Content, st.postContext.ref)
renderedPost = html2text.HTML2Text(renderedPost)
for key, plaintext := range plaintexts {
renderedPost = strings.Replace(renderedPost, key, plaintext, 1)
}
return renderedPost
}
func (st *StatusFormatter) mediaDescriptions(reblog bool) string {
var sb strings.Builder
status := st.status
mediaTag := st.prefs.MediaTag
if reblog {
status = status.Reblog
}
for _, item := range status.MediaAttachments {
if item.Description != "" {
sb.WriteString(fmt.Sprintf("\n%s [%s]", mediaTag, item.Description))
continue
}
sb.WriteString(mediaTag)
}
return sb.String()
}
func (st *StatusFormatter) username() string {
var sb strings.Builder
sb.WriteString("<")
sb.WriteString(st.status.Account.Username)
sb.WriteString(">")
return sb.String()
}
func (st *StatusFormatter) detailLine() string {
var sb strings.Builder
var items []string
if st.status.FavouritesCount > 0 {
items = append(items, fmt.Sprintf("Likes:%v", st.status.FavouritesCount))
}
if st.status.ReblogsCount > 0 {
items = append(items, fmt.Sprintf("Reblogs:%v", st.status.ReblogsCount))
}
if st.status.RepliesCount > 0 {
items = append(items, fmt.Sprintf("Replies:%v", st.status.RepliesCount))
}
for i := range items {
sb.WriteString(items[i])
if i != len(items)-1 {
sb.WriteString(" ")
}
}
return sb.String()
}
// Stringer for current index
type indexString struct {
*StatusFormatter
}
func (is *indexString) String() string {
if is.localindex != "" {
return fmt.Sprintf("%s>", is.localindex)
}
return fmt.Sprintf("%s%s>", is.postContext.prefix, is.postContext.ref)
}
// Stringer for user who caused a notification
type notificationUser struct {
*StatusFormatter
}
func (nu *notificationUser) String() string {
return fmt.Sprintf("[%s]", nu.notif.Account.Acct)
}
// Stringer for type of notification
type notificationType struct {
*StatusFormatter
}
func (nt *notificationType) String() string {
return fmt.Sprintf("[%s]", nt.notif.Type)
}
// Status content stringer
type statusContent struct {
*StatusFormatter
}
func (cf *statusContent) String() string {
return cf.statusContent(cf.status)
}
// Status boost content stringer
type boostContent struct {
*StatusFormatter
}
func (bc *boostContent) String() string {
return bc.reblogContent()
}
// Media descriptions for boosted posts stringer
type boostMediaDescriptions struct {
*StatusFormatter
}
func (bm *boostMediaDescriptions) String() string {
return bm.mediaDescriptions(true)
}
// Media description stringer
type mediaDescriptions struct {
*StatusFormatter
}
func (md *mediaDescriptions) String() string {
return md.mediaDescriptions(false)
}
// Post detail line (likes rts replies) stringer
type detailLine struct {
*StatusFormatter
}
func (dl *detailLine) String() string {
return dl.detailLine()
}
// Boosted username stringer
type boostedusername struct {
*StatusFormatter
}
func (usr *boostedusername) String() string {
var sb strings.Builder
sb.WriteString("<")
sb.WriteString(usr.status.Reblog.Account.Username)
sb.WriteString(">")
return sb.String()
}
// Formatted <username> stringer
type username struct {
*StatusFormatter
}
func (usr *username) String() string {
return usr.username()
}
func (pr *postref) renderStatus(content string, index string) (string, map[string]string) {
doc, err := html.Parse(strings.NewReader(content))
if err != nil {
fmt.Printf("Failed to parse status\n")
return "", nil
}
//clear out the url map
pr.urlmap[index] = []string{}
preformats := make(map[string]string)
for node := range doc.Descendants() {
if (node.Data == "pre" || node.Data == "") && node.FirstChild != nil {
preformats[fmt.Sprintf("%p%p", pr, node.FirstChild)] = node.FirstChild.Data
node.FirstChild.Data = fmt.Sprintf("%p%p", pr, node.FirstChild)
}
if node.Data == "a" && node.Type == html.ElementNode {
ismention := false
href := ""
for attr := range node.Attr {
if node.Attr[attr].Key == "class" && strings.Contains(node.Attr[attr].Val, "mention") {
node.Data = "div"
ismention = true
continue
}
if node.Attr[attr].Key == "href" {
href = node.Attr[attr].Val
//Replace the href with the description if the URL has one
if node.FirstChild != nil && node.FirstChild.Type == html.TextNode && !ismention {
node.Attr[attr].Val = fmt.Sprintf("(%s)", node.FirstChild.Data)
} else {
href = href + " "
}
}
}
if !ismention && href != "" {
pr.urlmap[index] = append(pr.urlmap[index], href)
refnode := &html.Node{
Type: html.TextNode,
Data: fmt.Sprintf(" [%v]", len(pr.urlmap[index]))}
if node.Parent != nil {
node.Parent.InsertBefore(refnode, node.NextSibling)
}
}
}
}
//Rip off the HTML body the parser made for us
for node := range doc.Descendants() {
if node.Data == "body" {
node.Type = html.DocumentNode
doc = node
}
}
var rendered bytes.Buffer
err = html.Render(&rendered, doc)
if err != nil {
return "", nil
}
renderedPlainText := rendered.String()
return renderedPlainText, preformats
}