Files
gogobee/internal/util/parser.go
prosolis 3369d7d8fe gofmt: bring internal/ and cmd/ back to gofmt -l clean
Mechanical `gofmt -w ./internal ./cmd`. Mostly struct-field realignment that
had drifted, plus a few trailing-newline fixes. No behaviour change — gofmt is
semantics-preserving, and build/vet/test are green either side.

Split out from the code-review fixes that follow so those stay reviewable
instead of hiding inside a wall of realignment.
2026-07-10 07:18:07 -07:00

124 lines
2.9 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package util
import (
"math"
"regexp"
"strconv"
"strings"
"unicode"
)
// MessageStats holds parsed message metrics.
type MessageStats struct {
Words int
Chars int
Links int
Images int
Questions int
Exclamations int
Emojis int
}
var (
linkRe = regexp.MustCompile(`https?://\S+`)
imageRe = regexp.MustCompile(`\.(png|jpg|jpeg|gif|webp|svg|bmp)(\?[^\s]*)?$`)
emojiRe = regexp.MustCompile(`[\x{1F600}-\x{1F64F}]|[\x{1F300}-\x{1F5FF}]|[\x{1F680}-\x{1F6FF}]|[\x{1F1E0}-\x{1F1FF}]|[\x{2600}-\x{26FF}]|[\x{2700}-\x{27BF}]`)
)
// ParseMessage extracts stats from a chat message body.
func ParseMessage(body string) MessageStats {
words := strings.Fields(body)
links := linkRe.FindAllString(body, -1)
images := 0
for _, l := range links {
if imageRe.MatchString(strings.ToLower(l)) {
images++
}
}
questions := strings.Count(body, "?")
exclamations := strings.Count(body, "!")
emojis := len(emojiRe.FindAllString(body, -1))
return MessageStats{
Words: len(words),
Chars: len([]rune(body)),
Links: len(links),
Images: images,
Questions: questions,
Exclamations: exclamations,
Emojis: emojis,
}
}
// XPForLevel returns the cumulative XP needed to reach level n.
// Uses quadratic curve: level² × 100.
func XPForLevel(level int) int {
return level * level * 100
}
// LevelFromXP returns the current level for a given XP total.
// Inverse of level² × 100: level = floor(sqrt(xp / 100)).
func LevelFromXP(xp int) int {
if xp <= 0 {
return 0
}
level := 0
for (level+1)*(level+1)*100 <= xp {
level++
}
return level
}
// ProgressBar returns a text progress bar like [#####-----] 50%.
func ProgressBar(current, max, width int) string {
if max <= 0 {
return strings.Repeat("-", width)
}
ratio := float64(current) / float64(max)
if ratio > 1 {
ratio = 1
}
filled := int(math.Round(ratio * float64(width)))
if filled > width {
filled = width
}
empty := width - filled
pct := int(ratio * 100)
return "[" + strings.Repeat("#", filled) + strings.Repeat("-", empty) + "] " + strconv.Itoa(pct) + "%"
}
// IsCommand checks if body starts with prefix+command (case-insensitive).
func IsCommand(body, prefix, command string) bool {
cmd := prefix + command
lower := strings.ToLower(strings.TrimSpace(body))
if lower == cmd {
return true
}
return strings.HasPrefix(lower, cmd+" ")
}
// GetArgs returns everything after the command prefix.
func GetArgs(body, prefix, command string) string {
cmd := prefix + command
trimmed := strings.TrimSpace(body)
lower := strings.ToLower(trimmed)
if !strings.HasPrefix(lower, cmd) {
return ""
}
rest := trimmed[len(cmd):]
return strings.TrimSpace(rest)
}
// HasNonASCII checks if string contains non-ASCII characters.
func HasNonASCII(s string) bool {
for _, r := range s {
if r > unicode.MaxASCII {
return true
}
}
return false
}