Dan Anglin
f223e0417c
All checks were successful
REUSE Compliance Check / check (push) Successful in 6s
View the status' summary/spoiler/content warning text if it exists.
277 lines
8.5 KiB
Go
277 lines
8.5 KiB
Go
package printer
|
|
|
|
import (
|
|
"math"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"codeflow.dananglin.me.uk/apollo/enbas/internal/model"
|
|
)
|
|
|
|
func (p Printer) PrintStatus(status model.Status, userAccountID string) {
|
|
var builder strings.Builder
|
|
|
|
// The account information
|
|
builder.WriteString("\n" + p.fullDisplayNameFormat(status.Account.DisplayName, status.Account.Acct))
|
|
|
|
// The ID of the status
|
|
builder.WriteString("\n\n" + p.headerFormat("STATUS ID:"))
|
|
builder.WriteString("\n" + status.ID)
|
|
|
|
// The subject, summary of content warning of the status
|
|
if status.SpoilerText != "" {
|
|
builder.WriteString("\n\n" + p.headerFormat("SUMMARY:"))
|
|
builder.WriteString("\n" + status.SpoilerText)
|
|
}
|
|
|
|
// The content of the status.
|
|
builder.WriteString("\n\n" + p.headerFormat("CONTENT:"))
|
|
builder.WriteString(p.convertHTMLToText(status.Content, true))
|
|
|
|
// Details of media attachments (if any).
|
|
if len(status.MediaAttachments) > 0 {
|
|
builder.WriteString("\n\n" + p.headerFormat("MEDIA ATTACHMENTS:"))
|
|
|
|
for ind, media := range status.MediaAttachments {
|
|
builder.WriteString("\n\n[" + strconv.Itoa(ind) + "] " + p.fieldFormat("ID:") + " " + media.ID)
|
|
builder.WriteString("\n " + p.fieldFormat("Type:") + " " + media.Type)
|
|
|
|
description := media.Description
|
|
if description == "" {
|
|
description = noMediaDescription
|
|
}
|
|
|
|
builder.WriteString("\n " + p.fieldFormat("Description:") + " " + description)
|
|
builder.WriteString("\n " + p.fieldFormat("Media URL:") + " " + media.URL)
|
|
}
|
|
}
|
|
|
|
// If a poll exists in a status, write the contents to the builder.
|
|
if status.Poll != nil {
|
|
pollOwner := false
|
|
if status.Account.ID == userAccountID {
|
|
pollOwner = true
|
|
}
|
|
|
|
builder.WriteString("\n\n" + p.headerFormat("POLL DETAILS:"))
|
|
builder.WriteString(p.pollDetails(*status.Poll, pollOwner))
|
|
}
|
|
|
|
// Status creation time
|
|
builder.WriteString("\n\n" + p.headerFormat("CREATED AT:"))
|
|
builder.WriteString("\n" + p.formatDateTime(status.CreatedAt))
|
|
|
|
// Status stats
|
|
builder.WriteString("\n\n" + p.headerFormat("STATS:"))
|
|
builder.WriteString("\n" + p.fieldFormat("Boosts: ") + strconv.Itoa(status.ReblogsCount))
|
|
builder.WriteString("\n" + p.fieldFormat("Likes: ") + strconv.Itoa(status.FavouritesCount))
|
|
builder.WriteString("\n" + p.fieldFormat("Replies: ") + strconv.Itoa(status.RepliesCount))
|
|
|
|
// The user's actions on the status
|
|
builder.WriteString("\n\n" + p.headerFormat("YOUR ACTIONS:"))
|
|
builder.WriteString("\n" + p.fieldFormat("Boosted: ") + strconv.FormatBool(status.Reblogged))
|
|
builder.WriteString("\n" + p.fieldFormat("Liked: ") + strconv.FormatBool(status.Favourited))
|
|
builder.WriteString("\n" + p.fieldFormat("Bookmarked: ") + strconv.FormatBool(status.Bookmarked))
|
|
builder.WriteString("\n" + p.fieldFormat("Muted: ") + strconv.FormatBool(status.Muted))
|
|
|
|
// Status visibility
|
|
builder.WriteString("\n\n" + p.headerFormat("VISIBILITY:"))
|
|
builder.WriteString("\n" + status.Visibility.String())
|
|
|
|
// Status URL
|
|
builder.WriteString("\n\n" + p.headerFormat("URL:"))
|
|
builder.WriteString("\n" + status.URL)
|
|
builder.WriteString("\n\n")
|
|
|
|
p.print(builder.String())
|
|
}
|
|
|
|
func (p Printer) PrintStatusList(list model.StatusList, userAccountID string) {
|
|
p.print(p.statusList(list, userAccountID))
|
|
}
|
|
|
|
func (p Printer) statusList(list model.StatusList, userAccountID string) string {
|
|
var builder strings.Builder
|
|
|
|
builder.WriteString(p.headerFormat(list.Name) + "\n")
|
|
|
|
for _, status := range list.Statuses {
|
|
statusID := status.ID
|
|
statusOwnerID := status.Account.ID
|
|
createdAt := p.formatDateTime(status.CreatedAt)
|
|
boostedAt := ""
|
|
content := status.Content
|
|
poll := status.Poll
|
|
mediaAttachments := status.MediaAttachments
|
|
summary := status.SpoilerText
|
|
|
|
switch {
|
|
case status.Reblog != nil:
|
|
builder.WriteString("\n" + p.wrapLines(
|
|
p.fullDisplayNameFormat(status.Account.DisplayName, status.Account.Acct)+
|
|
" boosted this status from "+
|
|
p.fullDisplayNameFormat(status.Reblog.Account.DisplayName, status.Reblog.Account.Acct)+
|
|
":",
|
|
0,
|
|
))
|
|
|
|
statusID = status.Reblog.ID
|
|
statusOwnerID = status.Reblog.Account.ID
|
|
createdAt = p.formatDateTime(status.Reblog.CreatedAt)
|
|
boostedAt = p.formatDateTime(status.CreatedAt)
|
|
content = status.Reblog.Content
|
|
poll = status.Reblog.Poll
|
|
mediaAttachments = status.Reblog.MediaAttachments
|
|
summary = status.Reblog.SpoilerText
|
|
|
|
case status.InReplyToID != "":
|
|
builder.WriteString("\n" + p.wrapLines(
|
|
p.fullDisplayNameFormat(status.Account.DisplayName, status.Account.Acct)+
|
|
" posted in reply to "+
|
|
status.InReplyToID+
|
|
":",
|
|
0,
|
|
))
|
|
default:
|
|
builder.WriteString("\n" + p.fullDisplayNameFormat(status.Account.DisplayName, status.Account.Acct) + " posted:")
|
|
}
|
|
|
|
if summary != "" {
|
|
builder.WriteString("\n\n" + p.bold(p.wrapLines(summary, 0)))
|
|
}
|
|
|
|
builder.WriteString("\n" + p.convertHTMLToText(content, true))
|
|
|
|
if poll != nil {
|
|
pollOwner := false
|
|
if statusOwnerID == userAccountID {
|
|
pollOwner = true
|
|
}
|
|
|
|
builder.WriteString(p.pollDetails(*poll, pollOwner))
|
|
}
|
|
|
|
for _, media := range mediaAttachments {
|
|
builder.WriteString("\n\n" + symbolImage + " " + p.fieldFormat("Media attachment: ") + media.ID)
|
|
builder.WriteString("\n " + p.fieldFormat("Media type: ") + media.Type + "\n")
|
|
|
|
description := " " + p.fieldFormat("Description: ")
|
|
|
|
if media.Description == "" {
|
|
description += noMediaDescription
|
|
} else {
|
|
description += media.Description
|
|
}
|
|
|
|
builder.WriteString(p.wrapLines(description, 2))
|
|
}
|
|
|
|
boosted := symbolBoosted
|
|
if status.Reblogged {
|
|
boosted = p.theme.boldyellow + symbolBoosted + p.theme.reset
|
|
}
|
|
|
|
liked := symbolNotLiked
|
|
if status.Favourited {
|
|
liked = p.theme.boldyellow + symbolLiked + p.theme.reset
|
|
}
|
|
|
|
bookmarked := symbolNotBookmarked
|
|
if status.Bookmarked {
|
|
bookmarked = p.theme.boldyellow + symbolBookmarked + p.theme.reset
|
|
}
|
|
|
|
builder.WriteString("\n\n" + boosted + " " + p.fieldFormat("boosted: ") + strconv.FormatBool(status.Reblogged))
|
|
builder.WriteString("\n" + liked + " " + p.fieldFormat("liked: ") + strconv.FormatBool(status.Favourited))
|
|
builder.WriteString("\n" + bookmarked + " " + p.fieldFormat("bookmarked: ") + strconv.FormatBool(status.Bookmarked))
|
|
|
|
builder.WriteString(
|
|
"\n\n" +
|
|
p.fieldFormat("Status ID: ") + statusID +
|
|
"\n" + p.fieldFormat("Created at: ") + createdAt,
|
|
)
|
|
|
|
if boostedAt != "" {
|
|
builder.WriteString("\n" + p.fieldFormat("Boosted at: ") + boostedAt)
|
|
}
|
|
|
|
builder.WriteString("\n" + p.statusSeparator + "\n")
|
|
}
|
|
|
|
return builder.String()
|
|
}
|
|
|
|
func (p Printer) pollDetails(poll model.Poll, owner bool) string {
|
|
var builder strings.Builder
|
|
|
|
for ind, option := range poll.Options {
|
|
var (
|
|
votage float64
|
|
percentage int
|
|
)
|
|
|
|
// Show the poll results under any of the following conditions:
|
|
// - the user is the owner of the poll
|
|
// - the poll has expired
|
|
// - the user has voted in the poll
|
|
if owner || poll.Expired || poll.Voted {
|
|
if poll.VotesCount == 0 {
|
|
percentage = 0
|
|
} else {
|
|
votage = float64(option.VotesCount) / float64(poll.VotesCount)
|
|
percentage = int(math.Floor(100 * votage))
|
|
}
|
|
|
|
optionTitle := "\n\n" + "[" + strconv.Itoa(ind) + "] " + option.Title
|
|
|
|
for _, vote := range poll.OwnVotes {
|
|
if ind == vote {
|
|
optionTitle += " " + symbolCheckMark
|
|
|
|
break
|
|
}
|
|
}
|
|
|
|
builder.WriteString(optionTitle)
|
|
builder.WriteString(p.pollMeter(votage))
|
|
builder.WriteString("\n" + strconv.Itoa(option.VotesCount) + " votes " + "(" + strconv.Itoa(percentage) + "%)")
|
|
} else {
|
|
builder.WriteString("\n" + "[" + strconv.Itoa(ind) + "] " + option.Title)
|
|
}
|
|
}
|
|
|
|
pollStatusField := "Poll is open until: "
|
|
if poll.Expired {
|
|
pollStatusField = "Poll was closed on: "
|
|
}
|
|
|
|
builder.WriteString("\n\n" + p.fieldFormat(pollStatusField) + p.formatDateTime(poll.ExpiredAt))
|
|
builder.WriteString("\n" + p.fieldFormat("Total votes: ") + strconv.Itoa(poll.VotesCount))
|
|
builder.WriteString("\n" + p.fieldFormat("Multiple choices allowed: ") + strconv.FormatBool(poll.Multiple))
|
|
|
|
return builder.String()
|
|
}
|
|
|
|
func (p Printer) pollMeter(votage float64) string {
|
|
numVoteBlocks := int(math.Floor(float64(p.lineWrapCharacterLimit) * votage))
|
|
numBackgroundBlocks := p.lineWrapCharacterLimit - numVoteBlocks
|
|
|
|
voteBlockColour := p.theme.boldgreen
|
|
backgroundBlockColor := p.theme.grey
|
|
|
|
if p.noColor {
|
|
voteBlockColour = p.theme.reset
|
|
|
|
if numVoteBlocks == 0 {
|
|
numVoteBlocks = 1
|
|
}
|
|
}
|
|
|
|
meter := "\n" + voteBlockColour + strings.Repeat(symbolPollMeter, numVoteBlocks) + p.theme.reset
|
|
|
|
if !p.noColor {
|
|
meter += backgroundBlockColor + strings.Repeat(symbolPollMeter, numBackgroundBlocks) + p.theme.reset
|
|
}
|
|
|
|
return meter
|
|
}
|