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(``,
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(``,
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
}