diff options
| author | s <[email protected]> | 2025-11-13 14:43:15 -0500 |
|---|---|---|
| committer | s <[email protected]> | 2025-11-13 14:43:15 -0500 |
| commit | 344a6f6415c3c1b593677adec3b8844e0839971b (patch) | |
| tree | b05291ecdf21917b27e9e234eeb997c2706966d5 /internal/formatter/reddit.go | |
| parent | a5fc01a03753c9a18ddeaf13610dd99b4b311b80 (diff) | |
| download | dborg-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.go | 444 |
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") +} |
