diff options
Diffstat (limited to 'internal/formatter/x.go')
| -rw-r--r-- | internal/formatter/x.go | 379 |
1 files changed, 379 insertions, 0 deletions
diff --git a/internal/formatter/x.go b/internal/formatter/x.go new file mode 100644 index 0000000..f701797 --- /dev/null +++ b/internal/formatter/x.go @@ -0,0 +1,379 @@ +package formatter + +import ( + "bytes" + "encoding/json" + "fmt" + "strings" + "text/tabwriter" + + "git.db.org.ai/dborg/internal/models" +) + +func FormatXHistory(resp *models.XResponse, 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 buf bytes.Buffer + + if resp.Error != "" { + fmt.Fprintf(&buf, "%s\n", Red(fmt.Sprintf("Error: %s", resp.Error))) + return buf.String(), nil + } + + fmt.Fprintf(&buf, "%s\n\n", Bold(Cyan(fmt.Sprintf("Username History for @%s", resp.Username)))) + + if len(resp.PreviousUsernames) == 0 { + fmt.Fprintf(&buf, "%s\n", Gray("No previous usernames found")) + } else { + w := tabwriter.NewWriter(&buf, 0, 0, 2, ' ', 0) + fmt.Fprintf(w, "%s\t%s\t%s\n", Bold("#"), Bold("Username"), Bold("Time Ago")) + + for i, u := range resp.PreviousUsernames { + fmt.Fprintf(w, "%s\t%s\t%s\n", + Yellow(fmt.Sprintf("%d", i+1)), + Cyan(fmt.Sprintf("@%s", u.Username)), + Gray(u.TimeAgo)) + } + w.Flush() + } + + fmt.Fprintf(&buf, "\n%s ", Blue("Credits Remaining:")) + if resp.Credits.Unlimited { + fmt.Fprintf(&buf, "%s\n", Green("Unlimited")) + } else { + fmt.Fprintf(&buf, "%s\n", FormatCredits(int64(resp.Credits.Remaining))) + } + + return buf.String(), nil +} + +func FormatXTweets(streamResp *models.TweetsStreamResponse, asJSON bool) (string, error) { + if asJSON { + data, err := json.MarshalIndent(streamResp, "", " ") + if err != nil { + return "", fmt.Errorf("failed to marshal JSON: %w", err) + } + return string(data), nil + } + + var buf bytes.Buffer + + if streamResp.Error != "" { + fmt.Fprintf(&buf, "%s\n", Red(fmt.Sprintf("Error: %s", streamResp.Error))) + return buf.String(), nil + } + + if streamResp.Tweet != nil { + tweet := streamResp.Tweet + + if tweet.Name != "" { + fmt.Fprintf(&buf, "%s ", Cyan(tweet.Name)) + } + if tweet.Handle != "" { + fmt.Fprintf(&buf, "%s", Gray(fmt.Sprintf("(@%s)", tweet.Handle))) + } + if tweet.Name != "" || tweet.Handle != "" { + fmt.Fprintf(&buf, "\n") + } + + if tweet.Text != "" { + fmt.Fprintf(&buf, "%s\n", Green(tweet.Text)) + } + + if tweet.Type != "" { + fmt.Fprintf(&buf, "%s %s\n", Dim("Type:"), tweet.Type) + } + + if tweet.URL != "" { + fmt.Fprintf(&buf, "%s\n", Blue(tweet.URL)) + } + + fmt.Fprintf(&buf, "%s\n", Dim("───")) + } + + if streamResp.Progress != nil { + fmt.Fprintf(&buf, "%s %s/%s\n", + Dim("Progress:"), + Yellow(fmt.Sprintf("%d", streamResp.Progress.Current)), + Yellow(fmt.Sprintf("%d", streamResp.Progress.Total))) + } + + if streamResp.Complete != nil { + fmt.Fprintf(&buf, "\n%s\n", Bold(Green("Complete!"))) + fmt.Fprintf(&buf, "%s %s\n", Dim("Duration:"), streamResp.Complete.Duration) + fmt.Fprintf(&buf, "%s %s\n", + Dim("Total Fetched:"), + Green(fmt.Sprintf("%d", streamResp.Complete.TotalFetched))) + if streamResp.Complete.TotalFailed > 0 { + fmt.Fprintf(&buf, "%s %s\n", + Dim("Total Failed:"), + Yellow(fmt.Sprintf("%d", streamResp.Complete.TotalFailed))) + } + } + + return buf.String(), nil +} + +func FormatXFirstFollowers(resp *models.FirstFollowersResponse, 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 buf bytes.Buffer + + fmt.Fprintf(&buf, "%s\n\n", Bold(Cyan(fmt.Sprintf("First 20 Followers of @%s", resp.Username)))) + + if len(resp.Followers) == 0 { + fmt.Fprintf(&buf, "%s\n", Gray("No followers found")) + } else { + w := tabwriter.NewWriter(&buf, 0, 0, 2, ' ', 0) + fmt.Fprintf(w, "%s\t%s\t%s\n", Bold("#"), Bold("Username"), Bold("Name")) + + for _, f := range resp.Followers { + fmt.Fprintf(w, "%s\t%s\t%s\n", + Yellow(fmt.Sprintf("%d", f.Number)), + Cyan(fmt.Sprintf("@%s", f.Username)), + Green(f.Name)) + } + w.Flush() + } + + fmt.Fprintf(&buf, "\n%s ", Blue("Credits Remaining:")) + if resp.Credits.Unlimited { + fmt.Fprintf(&buf, "%s\n", Green("Unlimited")) + } else { + fmt.Fprintf(&buf, "%s\n", FormatCredits(int64(resp.Credits.Remaining))) + } + + return buf.String(), nil +} + +func FormatXNotableFollowers(resp *models.NotableFollowersResponse, 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 buf bytes.Buffer + + fmt.Fprintf(&buf, "%s\n\n", Bold(Cyan(fmt.Sprintf("Notable Followers of @%s", resp.Username)))) + + if len(resp.Followers) == 0 { + fmt.Fprintf(&buf, "%s\n", Gray("No notable followers found")) + } else { + w := tabwriter.NewWriter(&buf, 0, 0, 2, ' ', 0) + fmt.Fprintf(w, "%s\t%s\t%s\n", Bold("Username"), Bold("Followers"), Bold("Score")) + + for _, f := range resp.Followers { + fmt.Fprintf(w, "%s\t%s\t%s\n", + Cyan(fmt.Sprintf("@%s", f.Username)), + Green(f.FollowerCount), + Magenta(fmt.Sprintf("%.2f", f.Score))) + } + w.Flush() + } + + fmt.Fprintf(&buf, "\n%s ", Blue("Credits Remaining:")) + if resp.Credits.Unlimited { + fmt.Fprintf(&buf, "%s\n", Green("Unlimited")) + } else { + fmt.Fprintf(&buf, "%s\n", FormatCredits(int64(resp.Credits.Remaining))) + } + + return buf.String(), nil +} + +func FormatXReplies(streamResp *models.TweetsStreamResponse, asJSON bool) (string, error) { + if asJSON { + data, err := json.MarshalIndent(streamResp, "", " ") + if err != nil { + return "", fmt.Errorf("failed to marshal JSON: %w", err) + } + return string(data), nil + } + + var buf bytes.Buffer + + if streamResp.Error != "" { + fmt.Fprintf(&buf, "%s\n", Red(fmt.Sprintf("Error: %s", streamResp.Error))) + return buf.String(), nil + } + + if streamResp.Tweet != nil { + tweet := streamResp.Tweet + + if tweet.Name != "" { + fmt.Fprintf(&buf, "%s ", Cyan(tweet.Name)) + } + if tweet.Handle != "" { + fmt.Fprintf(&buf, "%s", Gray(fmt.Sprintf("(@%s)", tweet.Handle))) + } + if tweet.Name != "" || tweet.Handle != "" { + fmt.Fprintf(&buf, "\n") + } + + if tweet.Text != "" { + lines := strings.Split(tweet.Text, "\n") + for _, line := range lines { + if len(line) > 80 { + wrapped := wrapText(line, 80) + for _, wLine := range wrapped { + fmt.Fprintf(&buf, "%s\n", Green(wLine)) + } + } else { + fmt.Fprintf(&buf, "%s\n", Green(line)) + } + } + } + + if tweet.URL != "" { + fmt.Fprintf(&buf, "%s\n", Blue(tweet.URL)) + } + + fmt.Fprintf(&buf, "%s\n", Dim("───")) + } + + if streamResp.Progress != nil { + fmt.Fprintf(&buf, "%s %s/%s\n", + Dim("Progress:"), + Yellow(fmt.Sprintf("%d", streamResp.Progress.Current)), + Yellow(fmt.Sprintf("%d", streamResp.Progress.Total))) + } + + if streamResp.Complete != nil { + fmt.Fprintf(&buf, "\n%s\n", Bold(Green("Complete!"))) + fmt.Fprintf(&buf, "%s %s\n", Dim("Duration:"), streamResp.Complete.Duration) + fmt.Fprintf(&buf, "%s %s\n", + Dim("Total Replies:"), + Green(fmt.Sprintf("%d", streamResp.Complete.TotalFetched))) + if streamResp.Complete.TotalFailed > 0 { + fmt.Fprintf(&buf, "%s %s\n", + Dim("Total Failed:"), + Yellow(fmt.Sprintf("%d", streamResp.Complete.TotalFailed))) + } + } + + return buf.String(), nil +} + +func FormatXSearch(streamResp *models.TweetsStreamResponse, asJSON bool) (string, error) { + if asJSON { + data, err := json.MarshalIndent(streamResp, "", " ") + if err != nil { + return "", fmt.Errorf("failed to marshal JSON: %w", err) + } + return string(data), nil + } + + var buf bytes.Buffer + + if streamResp.Error != "" { + fmt.Fprintf(&buf, "%s\n", Red(fmt.Sprintf("Error: %s", streamResp.Error))) + return buf.String(), nil + } + + if streamResp.Tweet != nil { + tweet := streamResp.Tweet + + if tweet.Name != "" { + fmt.Fprintf(&buf, "%s ", Cyan(tweet.Name)) + } + if tweet.Handle != "" { + fmt.Fprintf(&buf, "%s", Gray(fmt.Sprintf("(@%s)", tweet.Handle))) + } + if tweet.Name != "" || tweet.Handle != "" { + fmt.Fprintf(&buf, "\n") + } + + if tweet.Text != "" { + lines := strings.Split(tweet.Text, "\n") + for _, line := range lines { + if len(line) > 80 { + wrapped := wrapText(line, 80) + for _, wLine := range wrapped { + fmt.Fprintf(&buf, "%s\n", Green(wLine)) + } + } else { + fmt.Fprintf(&buf, "%s\n", Green(line)) + } + } + } + + if tweet.Type != "" { + fmt.Fprintf(&buf, "%s %s\n", Dim("Type:"), tweet.Type) + } + + if tweet.URL != "" { + fmt.Fprintf(&buf, "%s\n", Blue(tweet.URL)) + } + + fmt.Fprintf(&buf, "%s\n", Dim("───")) + } + + if streamResp.Progress != nil { + fmt.Fprintf(&buf, "%s %s/%s\n", + Dim("Progress:"), + Yellow(fmt.Sprintf("%d", streamResp.Progress.Current)), + Yellow(fmt.Sprintf("%d", streamResp.Progress.Total))) + } + + if streamResp.Complete != nil { + fmt.Fprintf(&buf, "\n%s\n", Bold(Green("Search Complete!"))) + fmt.Fprintf(&buf, "%s %s\n", Dim("Duration:"), streamResp.Complete.Duration) + fmt.Fprintf(&buf, "%s %s\n", + Dim("Total Results:"), + Green(fmt.Sprintf("%d", streamResp.Complete.TotalFetched))) + if streamResp.Complete.TotalFailed > 0 { + fmt.Fprintf(&buf, "%s %s\n", + Dim("Total Failed:"), + Yellow(fmt.Sprintf("%d", streamResp.Complete.TotalFailed))) + } + } + + return buf.String(), nil +} + +func wrapText(text string, width int) []string { + if len(text) <= width { + return []string{text} + } + + var lines []string + words := strings.Fields(text) + if len(words) == 0 { + return []string{text} + } + + var currentLine strings.Builder + for _, word := range words { + if currentLine.Len() == 0 { + currentLine.WriteString(word) + } else if currentLine.Len()+1+len(word) <= width { + currentLine.WriteString(" ") + currentLine.WriteString(word) + } else { + lines = append(lines, currentLine.String()) + currentLine.Reset() + currentLine.WriteString(word) + } + } + + if currentLine.Len() > 0 { + lines = append(lines, currentLine.String()) + } + + return lines +} |
