From 344a6f6415c3c1b593677adec3b8844e0839971b Mon Sep 17 00:00:00 2001 From: s Date: Thu, 13 Nov 2025 14:43:15 -0500 Subject: created pretty printing for all commands --- internal/formatter/breachforum.go | 186 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 186 insertions(+) create mode 100644 internal/formatter/breachforum.go (limited to 'internal/formatter/breachforum.go') diff --git a/internal/formatter/breachforum.go b/internal/formatter/breachforum.go new file mode 100644 index 0000000..6f77fdf --- /dev/null +++ b/internal/formatter/breachforum.go @@ -0,0 +1,186 @@ +package formatter + +import ( + "fmt" + "strings" + "time" + + "git.db.org.ai/dborg/internal/models" + "git.db.org.ai/dborg/internal/utils" +) + +func FormatBreachForumResults(response *models.BreachForumSearchResponse, asJSON bool) error { + if asJSON { + return utils.PrintJSON(response) + } + + PrintSection(fmt.Sprintf("BreachForum Search: %s", Bold(response.Query))) + fmt.Printf("%s: %d\n", Dim("Max Hits"), response.MaxHits) + + if response.Results == nil { + PrintWarning("No results found") + return nil + } + + results, ok := response.Results.(map[string]any) + if !ok { + return fmt.Errorf("unexpected results format") + } + + if elapsed, ok := results["elapsed_time_micros"].(float64); ok { + fmt.Printf("%s: %s\n", Dim("Search Time"), formatElapsedTime(elapsed)) + } + + if numHits, ok := results["num_hits"].(float64); ok { + fmt.Printf("%s: %s\n", Dim("Total Results"), Yellow(fmt.Sprintf("%d", int(numHits)))) + } + + fmt.Println() + + hits, ok := results["hits"].([]any) + if !ok || len(hits) == 0 { + PrintWarning("No results found") + return nil + } + + for i, hit := range hits { + if hitMap, ok := hit.(map[string]any); ok { + formatBreachHit(hitMap, i+1, len(hits)) + } + } + + if errors, ok := results["errors"].([]any); ok && len(errors) > 0 { + fmt.Printf("\n%s\n", Bold(Red("Errors"))) + for _, err := range errors { + fmt.Printf(" %s %s\n", StatusError.String(), err) + } + } + + return nil +} + +func formatBreachHit(hit map[string]any, index, total int) { + fmt.Printf("%s %s\n", Gray(fmt.Sprintf("[%d/%d]", index, total)), Bold("Result")) + + if author, ok := hit["author"].(string); ok && author != "" { + cleanAuthor := strings.TrimSpace(strings.TrimPrefix(author, " ")) + fmt.Printf(" %s: %s\n", Cyan("Author"), cleanAuthor) + } + + if source, ok := hit["source"].(string); ok && source != "" { + sourceColor := getSourceColor(source) + fmt.Printf(" %s: %s\n", Cyan("Source"), Colorize(source, sourceColor)) + } + + if hitType, ok := hit["type"].(string); ok && hitType != "" { + typeColor := getTypeColor(hitType) + fmt.Printf(" %s: %s\n", Cyan("Type"), Colorize(hitType, typeColor)) + } + + if detDate, ok := hit["detection_date"].(string); ok && detDate != "" { + formattedDate := formatDetectionDate(detDate) + fmt.Printf(" %s: %s\n", Cyan("Detected"), formattedDate) + } + + if content, ok := hit["content"].(string); ok && content != "" { + content = strings.TrimSpace(content) + if len(content) > 200 { + content = TruncateString(content, 200) + } + + lines := strings.Split(content, "\n") + fmt.Printf(" %s:\n", Cyan("Content")) + for _, line := range lines { + if strings.TrimSpace(line) != "" { + fmt.Printf(" %s\n", Dim(line)) + } + } + } + + fmt.Println() +} + +func formatElapsedTime(microseconds float64) string { + milliseconds := microseconds / 1000 + if milliseconds < 1000 { + return fmt.Sprintf("%.2fms", milliseconds) + } + seconds := milliseconds / 1000 + return fmt.Sprintf("%.2fs", seconds) +} + +func formatDetectionDate(dateStr string) string { + t, err := time.Parse(time.RFC3339, dateStr) + if err != nil { + return dateStr + } + + now := time.Now() + duration := now.Sub(t) + + var timeAgo string + switch { + case duration.Hours() < 1: + timeAgo = fmt.Sprintf("%d minutes ago", int(duration.Minutes())) + case duration.Hours() < 24: + timeAgo = fmt.Sprintf("%d hours ago", int(duration.Hours())) + case duration.Hours() < 168: + days := int(duration.Hours() / 24) + if days == 1 { + timeAgo = "1 day ago" + } else { + timeAgo = fmt.Sprintf("%d days ago", days) + } + case duration.Hours() < 730: + weeks := int(duration.Hours() / 168) + if weeks == 1 { + timeAgo = "1 week ago" + } else { + timeAgo = fmt.Sprintf("%d weeks ago", weeks) + } + default: + months := int(duration.Hours() / 730) + if months == 1 { + timeAgo = "1 month ago" + } else { + timeAgo = fmt.Sprintf("%d months ago", months) + } + } + + formattedDate := t.Format("2006-01-02 15:04") + return fmt.Sprintf("%s %s", Yellow(formattedDate), Gray(fmt.Sprintf("(%s)", timeAgo))) +} + +func getSourceColor(source string) string { + source = strings.ToLower(source) + switch { + case strings.Contains(source, "leakbase"): + return ColorRed + case strings.Contains(source, "blackhat"): + return ColorMagenta + case strings.Contains(source, "hard-tm"): + return ColorYellow + case strings.Contains(source, "htdark"): + return ColorGray + case strings.Contains(source, "crdcrew"): + return ColorBlue + default: + return ColorCyan + } +} + +func getTypeColor(hitType string) string { + hitType = strings.ToLower(hitType) + switch { + case strings.Contains(hitType, "credential"): + return ColorMagenta + case strings.Contains(hitType, "leak"): + return ColorRed + case strings.Contains(hitType, "breach"): + return ColorRed + case strings.Contains(hitType, "database"): + return ColorYellow + default: + return ColorCyan + } +} -- cgit v1.2.3