feat(chat): colorize usernames by ID; refine IDs/usernames and /name parsing
Server: - Generate 6-char IDs from lowercase hex alphabet "abcdef0123456789" - Default username now uses the ID as-is (no extra ToLower) - Use strings.CutPrefix for robust “/name ” command parsing Client: - Add colorizeLine to apply ANSI colors based on 6-hex-digit ID - Colorize chat lines: "<name> (<id>): <msg>" - Colorize [join]/[leave] notices - Colorize [rename] events - Colorize Welcome banner - Apply colorizeLine to incoming netMsg before appending Notes: - ID regex patterns accept 6 hex digits (case-insensitive), rendered in lowercase - Keeps non-matching lines unchanged
This commit is contained in:
parent
2c884d030a
commit
52d4602e33
42
main.go
42
main.go
@ -7,6 +7,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@ -16,6 +17,45 @@ import (
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
)
|
||||
|
||||
// colorizeLine applies ANSI styling to usernames based on 6-hex-digit id from the server.
|
||||
func colorizeLine(s string) string {
|
||||
reChat := regexp.MustCompile(`^(.+?) \(([0-9a-fA-F]{6})\):[ \t]*(.*)$`)
|
||||
if m := reChat.FindStringSubmatch(s); m != nil {
|
||||
id := strings.ToLower(m[2])
|
||||
nameStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("#" + id)).Bold(true)
|
||||
name := nameStyle.Render(m[1])
|
||||
rest := strings.TrimSpace(m[3])
|
||||
return fmt.Sprintf("%s (%s): %s", name, id, rest)
|
||||
}
|
||||
|
||||
reJoinLeave := regexp.MustCompile(`^\[(join|leave)\] (.+?) \(([0-9a-fA-F]{6})\)$`)
|
||||
if m := reJoinLeave.FindStringSubmatch(s); m != nil {
|
||||
id := strings.ToLower(m[3])
|
||||
nameStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("#" + id)).Bold(true)
|
||||
uname := nameStyle.Render(m[2])
|
||||
return fmt.Sprintf("[%s] %s (%s)", m[1], uname, id)
|
||||
}
|
||||
|
||||
reRename := regexp.MustCompile(`^\[rename\] (.+?) \(([0-9a-fA-F]{6})\) -> (.+)$`)
|
||||
if m := reRename.FindStringSubmatch(s); m != nil {
|
||||
id := strings.ToLower(m[2])
|
||||
nameStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("#" + id)).Bold(true)
|
||||
oldN := nameStyle.Render(m[1])
|
||||
newN := nameStyle.Render(m[3])
|
||||
return fmt.Sprintf("[rename] %s (%s) -> %s", oldN, id, newN)
|
||||
}
|
||||
|
||||
reWelcome := regexp.MustCompile(`^Welcome (.+?) \(([0-9a-fA-F]{6})\)$`)
|
||||
if m := reWelcome.FindStringSubmatch(s); m != nil {
|
||||
id := strings.ToLower(m[2])
|
||||
nameStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("#" + id)).Bold(true)
|
||||
uname := nameStyle.Render(m[1])
|
||||
return fmt.Sprintf("Welcome %s (%s)", uname, id)
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
type netMsg string
|
||||
type connectedMsg struct{ conn net.Conn }
|
||||
type disconnectedMsg struct{}
|
||||
@ -122,7 +162,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
return m, nil
|
||||
|
||||
case netMsg:
|
||||
m.messages = append(m.messages, string(msg))
|
||||
m.messages = append(m.messages, colorizeLine(string(msg)))
|
||||
m.refreshViewport()
|
||||
|
||||
if m.conn != nil && !strings.HasPrefix(string(msg), "[error] read") {
|
||||
|
||||
@ -102,14 +102,14 @@ func handleConn(h *Hub, c net.Conn) {
|
||||
h.joinCh <- c
|
||||
|
||||
// Generate per-connection ID
|
||||
id, err := gonanoid.Generate("ABCDEF0123456789", 6)
|
||||
id, err := gonanoid.Generate("abcdef0123456789", 6)
|
||||
if err != nil || id == "" {
|
||||
// Fallback to remote addr if generation fails
|
||||
id = c.RemoteAddr().String()
|
||||
}
|
||||
|
||||
// Default username is server-controlled; not necessarily unique
|
||||
defaultName := "user_" + strings.ToLower(id)
|
||||
defaultName := "user_" + id
|
||||
username := defaultName
|
||||
|
||||
// Greet client and instruct on setting username
|
||||
@ -132,8 +132,7 @@ func handleConn(h *Hub, c net.Conn) {
|
||||
if line == "/quit" {
|
||||
break // unified leave handling below
|
||||
}
|
||||
if strings.HasPrefix(line, "/name ") {
|
||||
desired := strings.TrimSpace(strings.TrimPrefix(line, "/name "))
|
||||
if desired, ok := strings.CutPrefix(line, "/name "); ok {
|
||||
newName := sanitizeUsername(desired)
|
||||
if newName == "" {
|
||||
fmt.Fprintln(c, "[error] invalid username")
|
||||
|
||||
Loading…
Reference in New Issue
Block a user