package badge import ( "crypto/sha256" "encoding/hex" "fmt" "html" "strings" "unicode/utf8" ) type Options struct { Label string Message string Extent string Preset string Article bool Style string Theme string Link string LeftColor string RightColor string } var extentColors = map[string]string{ "none": "#2f855a", "assisted": "#2b6cb0", "partial": "#b7791f", "mostly": "#c05621", "full": "#c53030", } var emojiPresetColors = map[string]string{ "no-ai": "#2f855a", "research": "#2b6cb0", "summary": "#b7791f", "full": "#c53030", } func Render(o Options) ([]byte, string) { label := truncate(strings.TrimSpace(o.Label), 40) if label == "" { label = "AI use" } message := truncate(strings.TrimSpace(o.Message), 80) if message == "" { message = o.Extent } if o.Theme == "emoji" { return renderEmoji(o, label, message) } style := o.Style if style != "flat-square" { style = "flat" } leftWidth := textWidth(label) rightWidth := textWidth(message) total := leftWidth + rightWidth radius := 3 if style == "flat-square" { radius = 0 } leftColor, rightColor := "#555555", extentColors[o.Extent] if rightColor == "" { rightColor = "#4a5568" } if o.Theme == "mono" { leftColor, rightColor = "#262626", "#666666" } if isHexColor(o.LeftColor) { leftColor = o.LeftColor } if isHexColor(o.RightColor) { rightColor = o.RightColor } title := html.EscapeString(label + ": " + message) labelEsc := html.EscapeString(label) messageEsc := html.EscapeString(message) openLink, closeLink := svgLink(o.Link) svg := fmt.Sprintf(`%s%s%s%s%s%s%s`, title, total, total, title, openLink, total, radius, leftWidth, leftColor, leftWidth, rightWidth, rightColor, total, leftWidth/2, labelEsc, leftWidth/2, labelEsc, leftWidth+rightWidth/2, messageEsc, leftWidth+rightWidth/2, messageEsc, closeLink) return withETag([]byte(svg)) } func renderEmoji(o Options, label, message string) ([]byte, string) { iconName := emojiIconName(o) background := emojiPresetColors[o.Preset] if o.Article { background = "#7c3aed" } if background == "" { background = extentColors[o.Extent] } if background == "" { background = "#4a5568" } foreground := "#ffffff" if isHexColor(o.LeftColor) { foreground = o.LeftColor } if isHexColor(o.RightColor) { background = o.RightColor } title := html.EscapeString(label + ": " + message) openLink, closeLink := svgLink(o.Link) icon := emojiIcon(iconName, foreground) svg := fmt.Sprintf(`%s%s%s%s`, title, iconName, title, openLink, background, icon, closeLink) return withETag([]byte(svg)) } func emojiIconName(o Options) string { if o.Article { return "article" } switch o.Preset { case "no-ai": return "human" case "research": return "research" case "summary": return "summary" case "full": return "bolt" } switch o.Extent { case "none": return "human" case "partial": return "partial" case "mostly": return "mostly" case "full": return "bolt" default: return "assisted" } } func emojiIcon(name, color string) string { stroke := ` fill="none" stroke="` + color + `" stroke-width="4.5" stroke-linecap="round" stroke-linejoin="round"` switch name { case "human": return `` case "research": return `` case "summary": return `` case "bolt": return `` case "article": return `` case "partial": return `` case "mostly": return `` default: return `` } } func svgLink(link string) (string, string) { if strings.HasPrefix(link, "https://") || strings.HasPrefix(link, "http://") { return ``, `` } return "", "" } func withETag(data []byte) ([]byte, string) { hash := sha256.Sum256(data) return data, `"` + hex.EncodeToString(hash[:12]) + `"` } func textWidth(s string) int { runes := utf8.RuneCountInString(s) width := runes*7 + 12 if width < 40 { width = 40 } return width } func truncate(s string, max int) string { r := []rune(s) if len(r) <= max { return s } return string(r[:max-1]) + "…" } func isHexColor(s string) bool { if len(s) != 7 || s[0] != '#' { return false } for _, r := range s[1:] { if !((r >= '0' && r <= '9') || (r >= 'a' && r <= 'f') || (r >= 'A' && r <= 'F')) { return false } } return true }