summaryrefslogtreecommitdiffstats
path: root/internal/formatter/reddit.go
diff options
context:
space:
mode:
authors <[email protected]>2025-11-13 14:43:15 -0500
committers <[email protected]>2025-11-13 14:43:15 -0500
commit344a6f6415c3c1b593677adec3b8844e0839971b (patch)
treeb05291ecdf21917b27e9e234eeb997c2706966d5 /internal/formatter/reddit.go
parenta5fc01a03753c9a18ddeaf13610dd99b4b311b80 (diff)
downloaddborg-344a6f6415c3c1b593677adec3b8844e0839971b.tar.gz
dborg-344a6f6415c3c1b593677adec3b8844e0839971b.zip
created pretty printing for all commandsv1.0.0
Diffstat (limited to 'internal/formatter/reddit.go')
-rw-r--r--internal/formatter/reddit.go444
1 files changed, 444 insertions, 0 deletions
diff --git a/internal/formatter/reddit.go b/internal/formatter/reddit.go
new file mode 100644
index 0000000..a696deb
--- /dev/null
+++ b/internal/formatter/reddit.go
@@ -0,0 +1,444 @@
+package formatter
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "html"
+ "strings"
+ "text/tabwriter"
+ "time"
+
+ "git.db.org.ai/dborg/internal/models"
+)
+
+func FormatRedditResults(resp interface{}, asJSON bool) (string, error) {
+ if asJSON {
+ data, err := json.MarshalIndent(resp, "", " ")
+ if err != nil {
+ return "", fmt.Errorf("failed to marshal JSON: %w", err)
+ }
+ return string(data), nil
+ }
+
+ var credits models.RedditCredits
+ var resultsData interface{}
+ var dataType string
+ var identifier string
+
+ switch r := resp.(type) {
+ case *models.SubredditResponse:
+ credits = r.Credits
+ resultsData = r.Results
+ dataType = r.Type
+ identifier = r.Subreddit
+ case *models.UserResponse:
+ credits = r.Credits
+ resultsData = r.Results
+ dataType = r.Type
+ identifier = r.Username
+ default:
+ return "", fmt.Errorf("unsupported response type")
+ }
+
+ var buf bytes.Buffer
+
+ switch dataType {
+ case "comments":
+ formatRedditComments(&buf, resultsData, dataType, identifier)
+ case "posts":
+ formatRedditPosts(&buf, resultsData, dataType, identifier)
+ case "about":
+ formatRedditUserAbout(&buf, resultsData, dataType, identifier)
+ default:
+ buf.WriteString(fmt.Sprintf("\n%s\n", Bold(Cyan(fmt.Sprintf("Reddit %s - %s", dataType, identifier)))))
+ buf.WriteString(fmt.Sprintf("%s\n\n", Gray(strings.Repeat("─", 80))))
+
+ if resultsData != nil {
+ resultsJSON, err := json.MarshalIndent(resultsData, "", " ")
+ if err != nil {
+ return "", fmt.Errorf("failed to marshal results: %w", err)
+ }
+ buf.WriteString(string(resultsJSON))
+ buf.WriteString("\n\n")
+ } else {
+ buf.WriteString(fmt.Sprintf("%s\n\n", Gray("No results found")))
+ }
+ }
+
+ if credits.Unlimited {
+ buf.WriteString(fmt.Sprintf("%s: %s\n", Dim("Credits"), Green("Unlimited")))
+ } else {
+ buf.WriteString(fmt.Sprintf("%s: %s\n", Dim("Credits Remaining"), FormatCredits(int64(credits.Remaining))))
+ }
+
+ return buf.String(), nil
+}
+
+func formatRedditUserAbout(buf *bytes.Buffer, resultsData interface{}, dataType, identifier string) {
+ fmt.Fprintf(buf, "\n%s\n", Bold(Cyan(fmt.Sprintf("Reddit User - u/%s", identifier))))
+ fmt.Fprintf(buf, "%s\n\n", Gray(strings.Repeat("─", 80)))
+
+ userData, ok := resultsData.(map[string]interface{})
+ if !ok {
+ fmt.Fprintf(buf, "%s\n", Gray("Invalid data structure"))
+ return
+ }
+
+ data, ok := userData["data"].(map[string]interface{})
+ if !ok {
+ fmt.Fprintf(buf, "%s\n", Gray("No user data found"))
+ return
+ }
+
+ w := tabwriter.NewWriter(buf, 0, 0, 2, ' ', 0)
+
+ if name := getStringValue(data, "name"); name != "" {
+ fmt.Fprintf(w, "%s\t%s\n", Cyan("Username:"), Bold(fmt.Sprintf("u/%s", name)))
+ }
+
+ if id := getStringValue(data, "id"); id != "" {
+ fmt.Fprintf(w, "%s\t%s\n", Cyan("User ID:"), Dim(id))
+ }
+
+ if created, ok := data["created_utc"].(float64); ok {
+ timestamp := time.Unix(int64(created), 0)
+ age := time.Since(timestamp)
+ years := int(age.Hours() / 24 / 365)
+ fmt.Fprintf(w, "%s\t%s (%s)\n",
+ Cyan("Account Age:"),
+ Gray(timestamp.Format("2006-01-02")),
+ Yellow(fmt.Sprintf("%d years", years)))
+ }
+
+ totalKarma := getIntValue(data, "total_karma")
+ linkKarma := getIntValue(data, "link_karma")
+ commentKarma := getIntValue(data, "comment_karma")
+ awardeeKarma := getIntValue(data, "awardee_karma")
+ awarderKarma := getIntValue(data, "awarder_karma")
+
+ fmt.Fprintf(w, "%s\t%s\n", Cyan("Total Karma:"), Bold(Green(fmt.Sprintf("%d", totalKarma))))
+ fmt.Fprintf(w, "%s\t%s\n", Cyan(" Post Karma:"), Green(fmt.Sprintf("%d", linkKarma)))
+ fmt.Fprintf(w, "%s\t%s\n", Cyan(" Comment Karma:"), Green(fmt.Sprintf("%d", commentKarma)))
+ if awardeeKarma > 0 {
+ fmt.Fprintf(w, "%s\t%s\n", Cyan(" Awardee Karma:"), Green(fmt.Sprintf("%d", awardeeKarma)))
+ }
+ if awarderKarma > 0 {
+ fmt.Fprintf(w, "%s\t%s\n", Cyan(" Awarder Karma:"), Green(fmt.Sprintf("%d", awarderKarma)))
+ }
+
+ badges := []string{}
+ if isMod, ok := data["is_mod"].(bool); ok && isMod {
+ badges = append(badges, Green("Moderator"))
+ }
+ if isEmployee, ok := data["is_employee"].(bool); ok && isEmployee {
+ badges = append(badges, Red("Reddit Employee"))
+ }
+ if isGold, ok := data["is_gold"].(bool); ok && isGold {
+ badges = append(badges, Yellow("Premium"))
+ }
+ if verified, ok := data["verified"].(bool); ok && verified {
+ badges = append(badges, Blue("Verified Email"))
+ }
+
+ if len(badges) > 0 {
+ fmt.Fprintf(w, "%s\t%s\n", Cyan("Badges:"), strings.Join(badges, " • "))
+ }
+
+ if acceptFollowers, ok := data["accept_followers"].(bool); ok {
+ if acceptFollowers {
+ fmt.Fprintf(w, "%s\t%s\n", Cyan("Followers:"), Green("Enabled"))
+ } else {
+ fmt.Fprintf(w, "%s\t%s\n", Cyan("Followers:"), Gray("Disabled"))
+ }
+ }
+
+ w.Flush()
+
+ if subreddit, ok := data["subreddit"].(map[string]interface{}); ok {
+ if publicDesc := getStringValue(subreddit, "public_description"); publicDesc != "" {
+ fmt.Fprintf(buf, "\n%s\n", Cyan("Bio:"))
+ publicDesc = html.UnescapeString(publicDesc)
+ lines := strings.Split(publicDesc, "\n")
+ for _, line := range lines {
+ if len(line) > 78 {
+ wrapped := wrapText(line, 76)
+ for _, wLine := range wrapped {
+ fmt.Fprintf(buf, " %s\n", wLine)
+ }
+ } else {
+ fmt.Fprintf(buf, " %s\n", line)
+ }
+ }
+ }
+ }
+
+ if iconImg := getStringValue(data, "icon_img"); iconImg != "" {
+ fmt.Fprintf(buf, "\n%s %s\n", Cyan("Avatar:"), Blue(iconImg))
+ }
+
+ fmt.Fprintf(buf, "\n")
+}
+
+func formatRedditPosts(buf *bytes.Buffer, resultsData interface{}, dataType, identifier string) {
+ fmt.Fprintf(buf, "\n%s\n", Bold(Cyan(fmt.Sprintf("Reddit %s - %s", dataType, identifier))))
+ fmt.Fprintf(buf, "%s\n\n", Gray(strings.Repeat("─", 80)))
+
+ listing, ok := resultsData.(map[string]interface{})
+ if !ok {
+ fmt.Fprintf(buf, "%s\n", Gray("Invalid data structure"))
+ return
+ }
+
+ data, ok := listing["data"].(map[string]interface{})
+ if !ok {
+ fmt.Fprintf(buf, "%s\n", Gray("No data found"))
+ return
+ }
+
+ children, ok := data["children"].([]interface{})
+ if !ok || len(children) == 0 {
+ fmt.Fprintf(buf, "%s\n", Gray("No posts found"))
+ return
+ }
+
+ for i, child := range children {
+ childMap, ok := child.(map[string]interface{})
+ if !ok {
+ continue
+ }
+
+ postData, ok := childMap["data"].(map[string]interface{})
+ if !ok {
+ continue
+ }
+
+ formatSinglePost(buf, postData, i+1)
+ }
+}
+
+func formatRedditComments(buf *bytes.Buffer, resultsData interface{}, dataType, identifier string) {
+ fmt.Fprintf(buf, "\n%s\n", Bold(Cyan(fmt.Sprintf("Reddit %s - %s", dataType, identifier))))
+ fmt.Fprintf(buf, "%s\n\n", Gray(strings.Repeat("─", 80)))
+
+ listing, ok := resultsData.(map[string]interface{})
+ if !ok {
+ fmt.Fprintf(buf, "%s\n", Gray("Invalid data structure"))
+ return
+ }
+
+ data, ok := listing["data"].(map[string]interface{})
+ if !ok {
+ fmt.Fprintf(buf, "%s\n", Gray("No data found"))
+ return
+ }
+
+ children, ok := data["children"].([]interface{})
+ if !ok || len(children) == 0 {
+ fmt.Fprintf(buf, "%s\n", Gray("No comments found"))
+ return
+ }
+
+ for i, child := range children {
+ childMap, ok := child.(map[string]interface{})
+ if !ok {
+ continue
+ }
+
+ commentData, ok := childMap["data"].(map[string]interface{})
+ if !ok {
+ continue
+ }
+
+ formatSingleComment(buf, commentData, i+1)
+ }
+}
+
+func formatSinglePost(buf *bytes.Buffer, post map[string]interface{}, num int) {
+ fmt.Fprintf(buf, "%s\n", Bold(Yellow(fmt.Sprintf("Post #%d", num))))
+
+ title := getStringValue(post, "title")
+ if title != "" {
+ title = html.UnescapeString(title)
+ fmt.Fprintf(buf, "%s\n", Bold(Green(title)))
+ }
+
+ w := tabwriter.NewWriter(buf, 0, 0, 2, ' ', 0)
+
+ author := getStringValue(post, "author")
+ subreddit := getStringValue(post, "subreddit")
+ if author != "" {
+ fmt.Fprintf(w, "%s\t%s", Cyan("Author:"), Bold(fmt.Sprintf("u/%s", author)))
+ if subreddit != "" {
+ fmt.Fprintf(w, " in %s", Bold(fmt.Sprintf("r/%s", subreddit)))
+ }
+ fmt.Fprintf(w, "\n")
+ }
+
+ if created, ok := post["created_utc"].(float64); ok {
+ timestamp := time.Unix(int64(created), 0)
+ fmt.Fprintf(w, "%s\t%s\n", Cyan("Posted:"), Gray(timestamp.Format("2006-01-02 15:04:05 MST")))
+ }
+
+ score := getIntValue(post, "score")
+ upvoteRatio := 0.0
+ if ratio, ok := post["upvote_ratio"].(float64); ok {
+ upvoteRatio = ratio
+ }
+ fmt.Fprintf(w, "%s\t%s", Cyan("Score:"), formatScore(score))
+ if upvoteRatio > 0 {
+ fmt.Fprintf(w, " (%s upvoted)", Dim(fmt.Sprintf("%.0f%%", upvoteRatio*100)))
+ }
+ fmt.Fprintf(w, "\n")
+
+ numComments := getIntValue(post, "num_comments")
+ fmt.Fprintf(w, "%s\t%s\n", Cyan("Comments:"), Yellow(fmt.Sprintf("%d", numComments)))
+
+ if stickied, ok := post["stickied"].(bool); ok && stickied {
+ fmt.Fprintf(w, "%s\t%s\n", Cyan("Status:"), Green("Stickied"))
+ }
+
+ if linkFlair := getStringValue(post, "link_flair_text"); linkFlair != "" {
+ fmt.Fprintf(w, "%s\t%s\n", Cyan("Flair:"), Magenta(linkFlair))
+ }
+
+ domain := getStringValue(post, "domain")
+ url := getStringValue(post, "url")
+ isSelf, _ := post["is_self"].(bool)
+
+ if !isSelf && url != "" {
+ fmt.Fprintf(w, "%s\t%s\n", Cyan("Link:"), Blue(url))
+ if domain != "" && !strings.HasPrefix(domain, "self.") {
+ fmt.Fprintf(w, "%s\t%s\n", Cyan("Domain:"), Dim(domain))
+ }
+ }
+
+ if permalink := getStringValue(post, "permalink"); permalink != "" {
+ fmt.Fprintf(w, "%s\t%s\n", Cyan("Reddit:"), Blue(fmt.Sprintf("https://reddit.com%s", permalink)))
+ }
+
+ w.Flush()
+
+ selftext := getStringValue(post, "selftext")
+ if selftext != "" && selftext != "[removed]" && selftext != "[deleted]" {
+ fmt.Fprintf(buf, "\n%s\n", Cyan("Content:"))
+ selftext = html.UnescapeString(selftext)
+
+ lines := strings.Split(selftext, "\n")
+ displayedLines := 0
+ maxLines := 15
+
+ for _, line := range lines {
+ if displayedLines >= maxLines {
+ remaining := len(lines) - displayedLines
+ fmt.Fprintf(buf, " %s\n", Dim(fmt.Sprintf("... (%d more lines)", remaining)))
+ break
+ }
+
+ if len(line) > 80 {
+ wrapped := wrapText(line, 78)
+ for _, wLine := range wrapped {
+ if displayedLines >= maxLines {
+ break
+ }
+ fmt.Fprintf(buf, " %s\n", wLine)
+ displayedLines++
+ }
+ } else {
+ fmt.Fprintf(buf, " %s\n", line)
+ displayedLines++
+ }
+ }
+ }
+
+ if edited := post["edited"]; edited != nil && edited != false {
+ fmt.Fprintf(buf, "\n%s\n", Dim("(edited)"))
+ }
+
+ fmt.Fprintf(buf, "\n%s\n\n", Gray(strings.Repeat("─", 80)))
+}
+
+func formatSingleComment(buf *bytes.Buffer, comment map[string]interface{}, num int) {
+ w := tabwriter.NewWriter(buf, 0, 0, 1, ' ', 0)
+
+ fmt.Fprintf(buf, "%s\n", Bold(Yellow(fmt.Sprintf("Comment #%d", num))))
+
+ author := getStringValue(comment, "author")
+ if author != "" {
+ fmt.Fprintf(w, "%s\t%s\n", Cyan("Author:"), Bold(author))
+ }
+
+ subreddit := getStringValue(comment, "subreddit")
+ if subreddit != "" {
+ fmt.Fprintf(w, "%s\tr/%s\n", Cyan("Subreddit:"), subreddit)
+ }
+
+ if created, ok := comment["created_utc"].(float64); ok {
+ timestamp := time.Unix(int64(created), 0)
+ fmt.Fprintf(w, "%s\t%s\n", Cyan("Posted:"), Gray(timestamp.Format("2006-01-02 15:04:05 MST")))
+ }
+
+ score := getIntValue(comment, "score")
+ fmt.Fprintf(w, "%s\t%s\n", Cyan("Score:"), formatScore(score))
+
+ if controversiality, ok := comment["controversiality"].(float64); ok && controversiality > 0 {
+ fmt.Fprintf(w, "%s\t%s\n", Cyan("Controversial:"), Yellow("Yes"))
+ }
+
+ w.Flush()
+
+ if linkTitle := getStringValue(comment, "link_title"); linkTitle != "" {
+ fmt.Fprintf(buf, "%s %s\n", Cyan("Post:"), Bold(linkTitle))
+ }
+
+ if permalink := getStringValue(comment, "permalink"); permalink != "" {
+ fmt.Fprintf(buf, "%s %s\n", Cyan("Link:"), Blue(fmt.Sprintf("https://reddit.com%s", permalink)))
+ }
+
+ body := getStringValue(comment, "body")
+ if body != "" {
+ fmt.Fprintf(buf, "\n%s\n", Cyan("Comment:"))
+ body = html.UnescapeString(body)
+
+ lines := strings.Split(body, "\n")
+ for _, line := range lines {
+ if len(line) > 80 {
+ wrapped := wrapText(line, 78)
+ for _, wLine := range wrapped {
+ fmt.Fprintf(buf, " %s\n", Green(wLine))
+ }
+ } else {
+ fmt.Fprintf(buf, " %s\n", Green(line))
+ }
+ }
+ }
+
+ if edited := comment["edited"]; edited != nil && edited != false {
+ fmt.Fprintf(buf, "\n%s\n", Dim("(edited)"))
+ }
+
+ fmt.Fprintf(buf, "\n%s\n\n", Gray(strings.Repeat("─", 80)))
+}
+
+func getStringValue(data map[string]interface{}, key string) string {
+ if val, ok := data[key].(string); ok {
+ return val
+ }
+ return ""
+}
+
+func getIntValue(data map[string]interface{}, key string) int {
+ if val, ok := data[key].(float64); ok {
+ return int(val)
+ }
+ return 0
+}
+
+func formatScore(score int) string {
+ if score > 0 {
+ return Green(fmt.Sprintf("+%d", score))
+ } else if score < 0 {
+ return Red(fmt.Sprintf("%d", score))
+ }
+ return Dim("0")
+}