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 }