Files
gogobee/internal/util/parser.go
prosolis 2c6f4e48c9 Add multi-tag personality archetype system
Replace single first-match archetype with comprehensive multi-tag system
spanning 25 archetypes across 8 categories (Communication, Temporal,
Emotional, Economy, Games, Adventure, Social). Archetypes are computed
nightly via cron job querying 15+ tables and cached in user_archetypes.
Thresholds calibrated against real community data. Integrates with
!personality, !superstatsexplusalpha, !whois, and milk carton flavor text.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 17:11:45 -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
}