Redo cli_analyzer
This commit is contained in:
parent
82f21abcff
commit
a3bf514806
1 changed files with 537 additions and 6 deletions
|
|
@ -86,18 +86,549 @@ func outputJSON(result *analyzer.AnalysisResult, writer io.Writer) error {
|
|||
|
||||
// outputHumanReadable outputs a human-readable summary
|
||||
func outputHumanReadable(result *analyzer.AnalysisResult, emailAnalyzer *analyzer.EmailAnalyzer, writer io.Writer) error {
|
||||
// Header
|
||||
report := result.Report
|
||||
|
||||
// Header with overall score
|
||||
fmt.Fprintln(writer, "\n"+strings.Repeat("=", 70))
|
||||
fmt.Fprintln(writer, "EMAIL DELIVERABILITY ANALYSIS REPORT")
|
||||
fmt.Fprintln(writer, strings.Repeat("=", 70))
|
||||
fmt.Fprintf(writer, "\nOverall Score: %d/100 (Grade: %s)\n", report.Score, report.Grade)
|
||||
fmt.Fprintf(writer, "Test ID: %s\n", report.TestId)
|
||||
fmt.Fprintf(writer, "Generated: %s\n", report.CreatedAt.Format("2006-01-02 15:04:05 MST"))
|
||||
|
||||
// Detailed checks
|
||||
fmt.Fprintln(writer, "\n"+strings.Repeat("-", 70))
|
||||
fmt.Fprintln(writer, "DETAILED CHECK RESULTS")
|
||||
fmt.Fprintln(writer, strings.Repeat("-", 70))
|
||||
// Score Summary
|
||||
if report.Summary != nil {
|
||||
fmt.Fprintln(writer, "\n"+strings.Repeat("-", 70))
|
||||
fmt.Fprintln(writer, "SCORE BREAKDOWN")
|
||||
fmt.Fprintln(writer, strings.Repeat("-", 70))
|
||||
|
||||
// TODO
|
||||
summary := report.Summary
|
||||
fmt.Fprintf(writer, " DNS Configuration: %3d%% (%s)\n",
|
||||
summary.DnsScore, summary.DnsGrade)
|
||||
fmt.Fprintf(writer, " Authentication: %3d%% (%s)\n",
|
||||
summary.AuthenticationScore, summary.AuthenticationGrade)
|
||||
fmt.Fprintf(writer, " Blacklist Status: %3d%% (%s)\n",
|
||||
summary.BlacklistScore, summary.BlacklistGrade)
|
||||
fmt.Fprintf(writer, " Header Quality: %3d%% (%s)\n",
|
||||
summary.HeaderScore, summary.HeaderGrade)
|
||||
fmt.Fprintf(writer, " Spam Score: %3d%% (%s)\n",
|
||||
summary.SpamScore, summary.SpamGrade)
|
||||
fmt.Fprintf(writer, " Content Quality: %3d%% (%s)\n",
|
||||
summary.ContentScore, summary.ContentGrade)
|
||||
}
|
||||
|
||||
// DNS Results
|
||||
if report.DnsResults != nil {
|
||||
fmt.Fprintln(writer, "\n"+strings.Repeat("-", 70))
|
||||
fmt.Fprintln(writer, "DNS CONFIGURATION")
|
||||
fmt.Fprintln(writer, strings.Repeat("-", 70))
|
||||
|
||||
dns := report.DnsResults
|
||||
fmt.Fprintf(writer, "\nFrom Domain: %s\n", dns.FromDomain)
|
||||
if dns.RpDomain != nil && *dns.RpDomain != dns.FromDomain {
|
||||
fmt.Fprintf(writer, "Return-Path Domain: %s\n", *dns.RpDomain)
|
||||
}
|
||||
|
||||
// MX Records
|
||||
if dns.FromMxRecords != nil && len(*dns.FromMxRecords) > 0 {
|
||||
fmt.Fprintln(writer, "\n MX Records (From Domain):")
|
||||
for _, mx := range *dns.FromMxRecords {
|
||||
status := "✓"
|
||||
if !mx.Valid {
|
||||
status = "✗"
|
||||
}
|
||||
fmt.Fprintf(writer, " %s [%d] %s", status, mx.Priority, mx.Host)
|
||||
if mx.Error != nil {
|
||||
fmt.Fprintf(writer, " - ERROR: %s", *mx.Error)
|
||||
}
|
||||
fmt.Fprintln(writer)
|
||||
}
|
||||
}
|
||||
|
||||
// SPF Records
|
||||
if dns.SpfRecords != nil && len(*dns.SpfRecords) > 0 {
|
||||
fmt.Fprintln(writer, "\n SPF Records:")
|
||||
for _, spf := range *dns.SpfRecords {
|
||||
status := "✓"
|
||||
if !spf.Valid {
|
||||
status = "✗"
|
||||
}
|
||||
fmt.Fprintf(writer, " %s ", status)
|
||||
if spf.Domain != nil {
|
||||
fmt.Fprintf(writer, "Domain: %s", *spf.Domain)
|
||||
}
|
||||
if spf.AllQualifier != nil {
|
||||
fmt.Fprintf(writer, " (all: %s)", *spf.AllQualifier)
|
||||
}
|
||||
fmt.Fprintln(writer)
|
||||
if spf.Record != nil {
|
||||
fmt.Fprintf(writer, " %s\n", *spf.Record)
|
||||
}
|
||||
if spf.Error != nil {
|
||||
fmt.Fprintf(writer, " ERROR: %s\n", *spf.Error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DKIM Records
|
||||
if dns.DkimRecords != nil && len(*dns.DkimRecords) > 0 {
|
||||
fmt.Fprintln(writer, "\n DKIM Records:")
|
||||
for _, dkim := range *dns.DkimRecords {
|
||||
status := "✓"
|
||||
if !dkim.Valid {
|
||||
status = "✗"
|
||||
}
|
||||
fmt.Fprintf(writer, " %s Selector: %s, Domain: %s\n", status, dkim.Selector, dkim.Domain)
|
||||
if dkim.Record != nil {
|
||||
fmt.Fprintf(writer, " %s\n", *dkim.Record)
|
||||
}
|
||||
if dkim.Error != nil {
|
||||
fmt.Fprintf(writer, " ERROR: %s\n", *dkim.Error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DMARC Record
|
||||
if dns.DmarcRecord != nil {
|
||||
fmt.Fprintln(writer, "\n DMARC Record:")
|
||||
status := "✓"
|
||||
if !dns.DmarcRecord.Valid {
|
||||
status = "✗"
|
||||
}
|
||||
fmt.Fprintf(writer, " %s Valid: %t", status, dns.DmarcRecord.Valid)
|
||||
if dns.DmarcRecord.Policy != nil {
|
||||
fmt.Fprintf(writer, ", Policy: %s", *dns.DmarcRecord.Policy)
|
||||
}
|
||||
if dns.DmarcRecord.SubdomainPolicy != nil {
|
||||
fmt.Fprintf(writer, ", Subdomain Policy: %s", *dns.DmarcRecord.SubdomainPolicy)
|
||||
}
|
||||
fmt.Fprintln(writer)
|
||||
if dns.DmarcRecord.Record != nil {
|
||||
fmt.Fprintf(writer, " %s\n", *dns.DmarcRecord.Record)
|
||||
}
|
||||
if dns.DmarcRecord.Error != nil {
|
||||
fmt.Fprintf(writer, " ERROR: %s\n", *dns.DmarcRecord.Error)
|
||||
}
|
||||
}
|
||||
|
||||
// BIMI Record
|
||||
if dns.BimiRecord != nil {
|
||||
fmt.Fprintln(writer, "\n BIMI Record:")
|
||||
status := "✓"
|
||||
if !dns.BimiRecord.Valid {
|
||||
status = "✗"
|
||||
}
|
||||
fmt.Fprintf(writer, " %s Valid: %t, Selector: %s, Domain: %s\n",
|
||||
status, dns.BimiRecord.Valid, dns.BimiRecord.Selector, dns.BimiRecord.Domain)
|
||||
if dns.BimiRecord.LogoUrl != nil {
|
||||
fmt.Fprintf(writer, " Logo URL: %s\n", *dns.BimiRecord.LogoUrl)
|
||||
}
|
||||
if dns.BimiRecord.VmcUrl != nil {
|
||||
fmt.Fprintf(writer, " VMC URL: %s\n", *dns.BimiRecord.VmcUrl)
|
||||
}
|
||||
if dns.BimiRecord.Record != nil {
|
||||
fmt.Fprintf(writer, " %s\n", *dns.BimiRecord.Record)
|
||||
}
|
||||
if dns.BimiRecord.Error != nil {
|
||||
fmt.Fprintf(writer, " ERROR: %s\n", *dns.BimiRecord.Error)
|
||||
}
|
||||
}
|
||||
|
||||
// PTR Records
|
||||
if dns.PtrRecords != nil && len(*dns.PtrRecords) > 0 {
|
||||
fmt.Fprintln(writer, "\n PTR (Reverse DNS) Records:")
|
||||
for _, ptr := range *dns.PtrRecords {
|
||||
fmt.Fprintf(writer, " %s\n", ptr)
|
||||
}
|
||||
}
|
||||
|
||||
// DNS Errors
|
||||
if dns.Errors != nil && len(*dns.Errors) > 0 {
|
||||
fmt.Fprintln(writer, "\n DNS Errors:")
|
||||
for _, err := range *dns.Errors {
|
||||
fmt.Fprintf(writer, " ! %s\n", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Authentication Results
|
||||
if report.Authentication != nil {
|
||||
fmt.Fprintln(writer, "\n"+strings.Repeat("-", 70))
|
||||
fmt.Fprintln(writer, "AUTHENTICATION RESULTS")
|
||||
fmt.Fprintln(writer, strings.Repeat("-", 70))
|
||||
|
||||
auth := report.Authentication
|
||||
|
||||
// SPF
|
||||
if auth.Spf != nil {
|
||||
fmt.Fprintf(writer, "\n SPF: %s", strings.ToUpper(string(auth.Spf.Result)))
|
||||
if auth.Spf.Domain != nil {
|
||||
fmt.Fprintf(writer, " (domain: %s)", *auth.Spf.Domain)
|
||||
}
|
||||
if auth.Spf.Details != nil {
|
||||
fmt.Fprintf(writer, "\n Details: %s", *auth.Spf.Details)
|
||||
}
|
||||
fmt.Fprintln(writer)
|
||||
}
|
||||
|
||||
// DKIM
|
||||
if auth.Dkim != nil && len(*auth.Dkim) > 0 {
|
||||
fmt.Fprintln(writer, "\n DKIM:")
|
||||
for i, dkim := range *auth.Dkim {
|
||||
fmt.Fprintf(writer, " [%d] %s", i+1, strings.ToUpper(string(dkim.Result)))
|
||||
if dkim.Domain != nil {
|
||||
fmt.Fprintf(writer, " (domain: %s", *dkim.Domain)
|
||||
if dkim.Selector != nil {
|
||||
fmt.Fprintf(writer, ", selector: %s", *dkim.Selector)
|
||||
}
|
||||
fmt.Fprintf(writer, ")")
|
||||
}
|
||||
if dkim.Details != nil {
|
||||
fmt.Fprintf(writer, "\n Details: %s", *dkim.Details)
|
||||
}
|
||||
fmt.Fprintln(writer)
|
||||
}
|
||||
}
|
||||
|
||||
// DMARC
|
||||
if auth.Dmarc != nil {
|
||||
fmt.Fprintf(writer, "\n DMARC: %s", strings.ToUpper(string(auth.Dmarc.Result)))
|
||||
if auth.Dmarc.Domain != nil {
|
||||
fmt.Fprintf(writer, " (domain: %s)", *auth.Dmarc.Domain)
|
||||
}
|
||||
if auth.Dmarc.Details != nil {
|
||||
fmt.Fprintf(writer, "\n Details: %s", *auth.Dmarc.Details)
|
||||
}
|
||||
fmt.Fprintln(writer)
|
||||
}
|
||||
|
||||
// ARC
|
||||
if auth.Arc != nil {
|
||||
fmt.Fprintf(writer, "\n ARC: %s", strings.ToUpper(string(auth.Arc.Result)))
|
||||
if auth.Arc.ChainLength != nil {
|
||||
fmt.Fprintf(writer, " (chain length: %d)", *auth.Arc.ChainLength)
|
||||
}
|
||||
if auth.Arc.ChainValid != nil {
|
||||
fmt.Fprintf(writer, " [valid: %t]", *auth.Arc.ChainValid)
|
||||
}
|
||||
if auth.Arc.Details != nil {
|
||||
fmt.Fprintf(writer, "\n Details: %s", *auth.Arc.Details)
|
||||
}
|
||||
fmt.Fprintln(writer)
|
||||
}
|
||||
|
||||
// BIMI
|
||||
if auth.Bimi != nil {
|
||||
fmt.Fprintf(writer, "\n BIMI: %s", strings.ToUpper(string(auth.Bimi.Result)))
|
||||
if auth.Bimi.Domain != nil {
|
||||
fmt.Fprintf(writer, " (domain: %s)", *auth.Bimi.Domain)
|
||||
}
|
||||
if auth.Bimi.Details != nil {
|
||||
fmt.Fprintf(writer, "\n Details: %s", *auth.Bimi.Details)
|
||||
}
|
||||
fmt.Fprintln(writer)
|
||||
}
|
||||
|
||||
// IP Reverse
|
||||
if auth.Iprev != nil {
|
||||
fmt.Fprintf(writer, "\n IP Reverse DNS: %s", strings.ToUpper(string(auth.Iprev.Result)))
|
||||
if auth.Iprev.Ip != nil {
|
||||
fmt.Fprintf(writer, " (ip: %s", *auth.Iprev.Ip)
|
||||
if auth.Iprev.Hostname != nil {
|
||||
fmt.Fprintf(writer, " -> %s", *auth.Iprev.Hostname)
|
||||
}
|
||||
fmt.Fprintf(writer, ")")
|
||||
}
|
||||
if auth.Iprev.Details != nil {
|
||||
fmt.Fprintf(writer, "\n Details: %s", *auth.Iprev.Details)
|
||||
}
|
||||
fmt.Fprintln(writer)
|
||||
}
|
||||
}
|
||||
|
||||
// Blacklist Results
|
||||
if report.Blacklists != nil && len(*report.Blacklists) > 0 {
|
||||
fmt.Fprintln(writer, "\n"+strings.Repeat("-", 70))
|
||||
fmt.Fprintln(writer, "BLACKLIST CHECKS")
|
||||
fmt.Fprintln(writer, strings.Repeat("-", 70))
|
||||
|
||||
totalChecks := 0
|
||||
totalListed := 0
|
||||
for ip, checks := range *report.Blacklists {
|
||||
totalChecks += len(checks)
|
||||
fmt.Fprintf(writer, "\n IP Address: %s\n", ip)
|
||||
for _, check := range checks {
|
||||
status := "✓"
|
||||
if check.Listed {
|
||||
status = "✗"
|
||||
totalListed++
|
||||
}
|
||||
fmt.Fprintf(writer, " %s %s", status, check.Rbl)
|
||||
if check.Listed {
|
||||
fmt.Fprintf(writer, " - LISTED")
|
||||
if check.Response != nil {
|
||||
fmt.Fprintf(writer, " (%s)", *check.Response)
|
||||
}
|
||||
} else {
|
||||
fmt.Fprintf(writer, " - OK")
|
||||
}
|
||||
fmt.Fprintln(writer)
|
||||
if check.Error != nil {
|
||||
fmt.Fprintf(writer, " ERROR: %s\n", *check.Error)
|
||||
}
|
||||
}
|
||||
}
|
||||
fmt.Fprintf(writer, "\n Summary: %d/%d blacklists triggered\n", totalListed, totalChecks)
|
||||
}
|
||||
|
||||
// Header Analysis
|
||||
if report.HeaderAnalysis != nil {
|
||||
fmt.Fprintln(writer, "\n"+strings.Repeat("-", 70))
|
||||
fmt.Fprintln(writer, "HEADER ANALYSIS")
|
||||
fmt.Fprintln(writer, strings.Repeat("-", 70))
|
||||
|
||||
header := report.HeaderAnalysis
|
||||
|
||||
// Domain Alignment
|
||||
if header.DomainAlignment != nil {
|
||||
fmt.Fprintln(writer, "\n Domain Alignment:")
|
||||
align := header.DomainAlignment
|
||||
if align.FromDomain != nil {
|
||||
fmt.Fprintf(writer, " From Domain: %s", *align.FromDomain)
|
||||
if align.FromOrgDomain != nil {
|
||||
fmt.Fprintf(writer, " (org: %s)", *align.FromOrgDomain)
|
||||
}
|
||||
fmt.Fprintln(writer)
|
||||
}
|
||||
if align.ReturnPathDomain != nil {
|
||||
fmt.Fprintf(writer, " Return-Path Domain: %s", *align.ReturnPathDomain)
|
||||
if align.ReturnPathOrgDomain != nil {
|
||||
fmt.Fprintf(writer, " (org: %s)", *align.ReturnPathOrgDomain)
|
||||
}
|
||||
fmt.Fprintln(writer)
|
||||
}
|
||||
if align.Aligned != nil {
|
||||
fmt.Fprintf(writer, " Strict Alignment: %t\n", *align.Aligned)
|
||||
}
|
||||
if align.RelaxedAligned != nil {
|
||||
fmt.Fprintf(writer, " Relaxed Alignment: %t\n", *align.RelaxedAligned)
|
||||
}
|
||||
if align.Issues != nil && len(*align.Issues) > 0 {
|
||||
fmt.Fprintln(writer, " Issues:")
|
||||
for _, issue := range *align.Issues {
|
||||
fmt.Fprintf(writer, " - %s\n", issue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Required/Important Headers
|
||||
if header.Headers != nil {
|
||||
fmt.Fprintln(writer, "\n Standard Headers:")
|
||||
importantHeaders := []string{"from", "to", "subject", "date", "message-id", "dkim-signature"}
|
||||
for _, hdrName := range importantHeaders {
|
||||
if hdr, ok := (*header.Headers)[hdrName]; ok {
|
||||
status := "✗"
|
||||
if hdr.Present {
|
||||
status = "✓"
|
||||
}
|
||||
fmt.Fprintf(writer, " %s %s: ", status, strings.ToUpper(hdrName))
|
||||
if hdr.Present {
|
||||
if hdr.Valid != nil && !*hdr.Valid {
|
||||
fmt.Fprintf(writer, "INVALID")
|
||||
} else {
|
||||
fmt.Fprintf(writer, "OK")
|
||||
}
|
||||
if hdr.Importance != nil {
|
||||
fmt.Fprintf(writer, " [%s]", *hdr.Importance)
|
||||
}
|
||||
} else {
|
||||
fmt.Fprintf(writer, "MISSING")
|
||||
}
|
||||
fmt.Fprintln(writer)
|
||||
if hdr.Issues != nil && len(*hdr.Issues) > 0 {
|
||||
for _, issue := range *hdr.Issues {
|
||||
fmt.Fprintf(writer, " - %s\n", issue)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Header Issues
|
||||
if header.Issues != nil && len(*header.Issues) > 0 {
|
||||
fmt.Fprintln(writer, "\n Header Issues:")
|
||||
for _, issue := range *header.Issues {
|
||||
fmt.Fprintf(writer, " [%s] %s: %s\n",
|
||||
strings.ToUpper(string(issue.Severity)), issue.Header, issue.Message)
|
||||
if issue.Advice != nil {
|
||||
fmt.Fprintf(writer, " Advice: %s\n", *issue.Advice)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Received Chain
|
||||
if header.ReceivedChain != nil && len(*header.ReceivedChain) > 0 {
|
||||
fmt.Fprintln(writer, "\n Email Path (Received Chain):")
|
||||
for i, hop := range *header.ReceivedChain {
|
||||
fmt.Fprintf(writer, " [%d] ", i+1)
|
||||
if hop.From != nil {
|
||||
fmt.Fprintf(writer, "%s", *hop.From)
|
||||
if hop.Ip != nil {
|
||||
fmt.Fprintf(writer, " (%s)", *hop.Ip)
|
||||
}
|
||||
}
|
||||
if hop.By != nil {
|
||||
fmt.Fprintf(writer, " -> %s", *hop.By)
|
||||
}
|
||||
fmt.Fprintln(writer)
|
||||
if hop.Timestamp != nil {
|
||||
fmt.Fprintf(writer, " Time: %s\n", hop.Timestamp.Format("2006-01-02 15:04:05 MST"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SpamAssassin Results
|
||||
if report.Spamassassin != nil {
|
||||
fmt.Fprintln(writer, "\n"+strings.Repeat("-", 70))
|
||||
fmt.Fprintln(writer, "SPAMASSASSIN ANALYSIS")
|
||||
fmt.Fprintln(writer, strings.Repeat("-", 70))
|
||||
|
||||
sa := report.Spamassassin
|
||||
fmt.Fprintf(writer, "\n Score: %.2f / %.2f", sa.Score, sa.RequiredScore)
|
||||
if sa.IsSpam {
|
||||
fmt.Fprintf(writer, " (SPAM)")
|
||||
} else {
|
||||
fmt.Fprintf(writer, " (HAM)")
|
||||
}
|
||||
fmt.Fprintln(writer)
|
||||
|
||||
if sa.Version != nil {
|
||||
fmt.Fprintf(writer, " Version: %s\n", *sa.Version)
|
||||
}
|
||||
|
||||
if len(sa.TestDetails) > 0 {
|
||||
fmt.Fprintln(writer, "\n Triggered Tests:")
|
||||
for _, test := range sa.TestDetails {
|
||||
scoreStr := "+"
|
||||
if test.Score < 0 {
|
||||
scoreStr = ""
|
||||
}
|
||||
fmt.Fprintf(writer, " [%s%.2f] %s", scoreStr, test.Score, test.Name)
|
||||
if test.Description != nil {
|
||||
fmt.Fprintf(writer, "\n %s", *test.Description)
|
||||
}
|
||||
fmt.Fprintln(writer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Content Analysis
|
||||
if report.ContentAnalysis != nil {
|
||||
fmt.Fprintln(writer, "\n"+strings.Repeat("-", 70))
|
||||
fmt.Fprintln(writer, "CONTENT ANALYSIS")
|
||||
fmt.Fprintln(writer, strings.Repeat("-", 70))
|
||||
|
||||
content := report.ContentAnalysis
|
||||
|
||||
// Basic content info
|
||||
fmt.Fprintln(writer, "\n Content Structure:")
|
||||
if content.HasPlaintext != nil {
|
||||
fmt.Fprintf(writer, " Has Plaintext: %t\n", *content.HasPlaintext)
|
||||
}
|
||||
if content.HasHtml != nil {
|
||||
fmt.Fprintf(writer, " Has HTML: %t\n", *content.HasHtml)
|
||||
}
|
||||
if content.TextToImageRatio != nil {
|
||||
fmt.Fprintf(writer, " Text-to-Image Ratio: %.2f\n", *content.TextToImageRatio)
|
||||
}
|
||||
|
||||
// Unsubscribe
|
||||
if content.HasUnsubscribeLink != nil {
|
||||
fmt.Fprintf(writer, " Has Unsubscribe Link: %t\n", *content.HasUnsubscribeLink)
|
||||
if *content.HasUnsubscribeLink && content.UnsubscribeMethods != nil && len(*content.UnsubscribeMethods) > 0 {
|
||||
fmt.Fprintf(writer, " Unsubscribe Methods: ")
|
||||
for i, method := range *content.UnsubscribeMethods {
|
||||
if i > 0 {
|
||||
fmt.Fprintf(writer, ", ")
|
||||
}
|
||||
fmt.Fprintf(writer, "%s", method)
|
||||
}
|
||||
fmt.Fprintln(writer)
|
||||
}
|
||||
}
|
||||
|
||||
// Links
|
||||
if content.Links != nil && len(*content.Links) > 0 {
|
||||
fmt.Fprintf(writer, "\n Links (%d total):\n", len(*content.Links))
|
||||
for _, link := range *content.Links {
|
||||
status := ""
|
||||
switch link.Status {
|
||||
case "valid":
|
||||
status = "✓"
|
||||
case "broken":
|
||||
status = "✗"
|
||||
case "suspicious":
|
||||
status = "⚠"
|
||||
case "redirected":
|
||||
status = "→"
|
||||
case "timeout":
|
||||
status = "⏱"
|
||||
}
|
||||
fmt.Fprintf(writer, " %s [%s] %s", status, link.Status, link.Url)
|
||||
if link.HttpCode != nil {
|
||||
fmt.Fprintf(writer, " (HTTP %d)", *link.HttpCode)
|
||||
}
|
||||
fmt.Fprintln(writer)
|
||||
if link.RedirectChain != nil && len(*link.RedirectChain) > 0 {
|
||||
fmt.Fprintln(writer, " Redirect chain:")
|
||||
for _, url := range *link.RedirectChain {
|
||||
fmt.Fprintf(writer, " -> %s\n", url)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Images
|
||||
if content.Images != nil && len(*content.Images) > 0 {
|
||||
fmt.Fprintf(writer, "\n Images (%d total):\n", len(*content.Images))
|
||||
missingAlt := 0
|
||||
trackingPixels := 0
|
||||
for _, img := range *content.Images {
|
||||
if !img.HasAlt {
|
||||
missingAlt++
|
||||
}
|
||||
if img.IsTrackingPixel != nil && *img.IsTrackingPixel {
|
||||
trackingPixels++
|
||||
}
|
||||
}
|
||||
fmt.Fprintf(writer, " Images with ALT text: %d/%d\n",
|
||||
len(*content.Images)-missingAlt, len(*content.Images))
|
||||
if trackingPixels > 0 {
|
||||
fmt.Fprintf(writer, " Tracking pixels detected: %d\n", trackingPixels)
|
||||
}
|
||||
}
|
||||
|
||||
// HTML Issues
|
||||
if content.HtmlIssues != nil && len(*content.HtmlIssues) > 0 {
|
||||
fmt.Fprintln(writer, "\n Content Issues:")
|
||||
for _, issue := range *content.HtmlIssues {
|
||||
fmt.Fprintf(writer, " [%s] %s: %s\n",
|
||||
strings.ToUpper(string(issue.Severity)), issue.Type, issue.Message)
|
||||
if issue.Location != nil {
|
||||
fmt.Fprintf(writer, " Location: %s\n", *issue.Location)
|
||||
}
|
||||
if issue.Advice != nil {
|
||||
fmt.Fprintf(writer, " Advice: %s\n", *issue.Advice)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Footer
|
||||
fmt.Fprintln(writer, "\n"+strings.Repeat("=", 70))
|
||||
fmt.Fprintf(writer, "Report generated by happyDeliver - https://happydeliver.org\n")
|
||||
fmt.Fprintln(writer, strings.Repeat("=", 70))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue