create babble package
This commit is contained in:
parent
089246dd25
commit
906042a67e
350
internal/babble/babble.go
Normal file
350
internal/babble/babble.go
Normal file
@ -0,0 +1,350 @@
|
||||
package babble
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"dk/internal/database"
|
||||
|
||||
"zombiezen.com/go/sqlite"
|
||||
)
|
||||
|
||||
// Babble represents a global chat message in the database
|
||||
type Babble struct {
|
||||
ID int `json:"id"`
|
||||
Posted int64 `json:"posted"`
|
||||
Author string `json:"author"`
|
||||
Babble string `json:"babble"`
|
||||
|
||||
db *database.DB
|
||||
}
|
||||
|
||||
// Find retrieves a babble message by ID
|
||||
func Find(db *database.DB, id int) (*Babble, error) {
|
||||
babble := &Babble{db: db}
|
||||
|
||||
query := "SELECT id, posted, author, babble FROM babble WHERE id = ?"
|
||||
err := db.Query(query, func(stmt *sqlite.Stmt) error {
|
||||
babble.ID = stmt.ColumnInt(0)
|
||||
babble.Posted = stmt.ColumnInt64(1)
|
||||
babble.Author = stmt.ColumnText(2)
|
||||
babble.Babble = stmt.ColumnText(3)
|
||||
return nil
|
||||
}, id)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to find babble: %w", err)
|
||||
}
|
||||
|
||||
if babble.ID == 0 {
|
||||
return nil, fmt.Errorf("babble with ID %d not found", id)
|
||||
}
|
||||
|
||||
return babble, nil
|
||||
}
|
||||
|
||||
// All retrieves all babble messages ordered by posted time (newest first)
|
||||
func All(db *database.DB) ([]*Babble, error) {
|
||||
var babbles []*Babble
|
||||
|
||||
query := "SELECT id, posted, author, babble FROM babble ORDER BY posted DESC, id DESC"
|
||||
err := db.Query(query, func(stmt *sqlite.Stmt) error {
|
||||
babble := &Babble{
|
||||
ID: stmt.ColumnInt(0),
|
||||
Posted: stmt.ColumnInt64(1),
|
||||
Author: stmt.ColumnText(2),
|
||||
Babble: stmt.ColumnText(3),
|
||||
db: db,
|
||||
}
|
||||
babbles = append(babbles, babble)
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to retrieve all babble: %w", err)
|
||||
}
|
||||
|
||||
return babbles, nil
|
||||
}
|
||||
|
||||
// ByAuthor retrieves babble messages by a specific author
|
||||
func ByAuthor(db *database.DB, author string) ([]*Babble, error) {
|
||||
var babbles []*Babble
|
||||
|
||||
query := "SELECT id, posted, author, babble FROM babble WHERE LOWER(author) = LOWER(?) ORDER BY posted DESC, id DESC"
|
||||
err := db.Query(query, func(stmt *sqlite.Stmt) error {
|
||||
babble := &Babble{
|
||||
ID: stmt.ColumnInt(0),
|
||||
Posted: stmt.ColumnInt64(1),
|
||||
Author: stmt.ColumnText(2),
|
||||
Babble: stmt.ColumnText(3),
|
||||
db: db,
|
||||
}
|
||||
babbles = append(babbles, babble)
|
||||
return nil
|
||||
}, author)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to retrieve babble by author: %w", err)
|
||||
}
|
||||
|
||||
return babbles, nil
|
||||
}
|
||||
|
||||
// Recent retrieves the most recent babble messages (limited by count)
|
||||
func Recent(db *database.DB, limit int) ([]*Babble, error) {
|
||||
var babbles []*Babble
|
||||
|
||||
query := "SELECT id, posted, author, babble FROM babble ORDER BY posted DESC, id DESC LIMIT ?"
|
||||
err := db.Query(query, func(stmt *sqlite.Stmt) error {
|
||||
babble := &Babble{
|
||||
ID: stmt.ColumnInt(0),
|
||||
Posted: stmt.ColumnInt64(1),
|
||||
Author: stmt.ColumnText(2),
|
||||
Babble: stmt.ColumnText(3),
|
||||
db: db,
|
||||
}
|
||||
babbles = append(babbles, babble)
|
||||
return nil
|
||||
}, limit)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to retrieve recent babble: %w", err)
|
||||
}
|
||||
|
||||
return babbles, nil
|
||||
}
|
||||
|
||||
// Since retrieves babble messages since a specific timestamp
|
||||
func Since(db *database.DB, since int64) ([]*Babble, error) {
|
||||
var babbles []*Babble
|
||||
|
||||
query := "SELECT id, posted, author, babble FROM babble WHERE posted >= ? ORDER BY posted DESC, id DESC"
|
||||
err := db.Query(query, func(stmt *sqlite.Stmt) error {
|
||||
babble := &Babble{
|
||||
ID: stmt.ColumnInt(0),
|
||||
Posted: stmt.ColumnInt64(1),
|
||||
Author: stmt.ColumnText(2),
|
||||
Babble: stmt.ColumnText(3),
|
||||
db: db,
|
||||
}
|
||||
babbles = append(babbles, babble)
|
||||
return nil
|
||||
}, since)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to retrieve babble since timestamp: %w", err)
|
||||
}
|
||||
|
||||
return babbles, nil
|
||||
}
|
||||
|
||||
// Between retrieves babble messages between two timestamps (inclusive)
|
||||
func Between(db *database.DB, start, end int64) ([]*Babble, error) {
|
||||
var babbles []*Babble
|
||||
|
||||
query := "SELECT id, posted, author, babble FROM babble WHERE posted >= ? AND posted <= ? ORDER BY posted DESC, id DESC"
|
||||
err := db.Query(query, func(stmt *sqlite.Stmt) error {
|
||||
babble := &Babble{
|
||||
ID: stmt.ColumnInt(0),
|
||||
Posted: stmt.ColumnInt64(1),
|
||||
Author: stmt.ColumnText(2),
|
||||
Babble: stmt.ColumnText(3),
|
||||
db: db,
|
||||
}
|
||||
babbles = append(babbles, babble)
|
||||
return nil
|
||||
}, start, end)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to retrieve babble between timestamps: %w", err)
|
||||
}
|
||||
|
||||
return babbles, nil
|
||||
}
|
||||
|
||||
// Search retrieves babble messages containing the search term (case-insensitive)
|
||||
func Search(db *database.DB, term string) ([]*Babble, error) {
|
||||
var babbles []*Babble
|
||||
|
||||
query := "SELECT id, posted, author, babble FROM babble WHERE LOWER(babble) LIKE LOWER(?) ORDER BY posted DESC, id DESC"
|
||||
searchTerm := "%" + term + "%"
|
||||
|
||||
err := db.Query(query, func(stmt *sqlite.Stmt) error {
|
||||
babble := &Babble{
|
||||
ID: stmt.ColumnInt(0),
|
||||
Posted: stmt.ColumnInt64(1),
|
||||
Author: stmt.ColumnText(2),
|
||||
Babble: stmt.ColumnText(3),
|
||||
db: db,
|
||||
}
|
||||
babbles = append(babbles, babble)
|
||||
return nil
|
||||
}, searchTerm)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to search babble: %w", err)
|
||||
}
|
||||
|
||||
return babbles, nil
|
||||
}
|
||||
|
||||
// RecentByAuthor retrieves recent messages from a specific author
|
||||
func RecentByAuthor(db *database.DB, author string, limit int) ([]*Babble, error) {
|
||||
var babbles []*Babble
|
||||
|
||||
query := "SELECT id, posted, author, babble FROM babble WHERE LOWER(author) = LOWER(?) ORDER BY posted DESC, id DESC LIMIT ?"
|
||||
err := db.Query(query, func(stmt *sqlite.Stmt) error {
|
||||
babble := &Babble{
|
||||
ID: stmt.ColumnInt(0),
|
||||
Posted: stmt.ColumnInt64(1),
|
||||
Author: stmt.ColumnText(2),
|
||||
Babble: stmt.ColumnText(3),
|
||||
db: db,
|
||||
}
|
||||
babbles = append(babbles, babble)
|
||||
return nil
|
||||
}, author, limit)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to retrieve recent babble by author: %w", err)
|
||||
}
|
||||
|
||||
return babbles, nil
|
||||
}
|
||||
|
||||
// Save updates an existing babble message in the database
|
||||
func (b *Babble) Save() error {
|
||||
if b.ID == 0 {
|
||||
return fmt.Errorf("cannot save babble without ID")
|
||||
}
|
||||
|
||||
query := `UPDATE babble SET posted = ?, author = ?, babble = ? WHERE id = ?`
|
||||
return b.db.Exec(query, b.Posted, b.Author, b.Babble, b.ID)
|
||||
}
|
||||
|
||||
// Delete removes the babble message from the database
|
||||
func (b *Babble) Delete() error {
|
||||
if b.ID == 0 {
|
||||
return fmt.Errorf("cannot delete babble without ID")
|
||||
}
|
||||
|
||||
query := "DELETE FROM babble WHERE id = ?"
|
||||
return b.db.Exec(query, b.ID)
|
||||
}
|
||||
|
||||
// PostedTime returns the posted timestamp as a time.Time
|
||||
func (b *Babble) PostedTime() time.Time {
|
||||
return time.Unix(b.Posted, 0)
|
||||
}
|
||||
|
||||
// SetPostedTime sets the posted timestamp from a time.Time
|
||||
func (b *Babble) SetPostedTime(t time.Time) {
|
||||
b.Posted = t.Unix()
|
||||
}
|
||||
|
||||
// IsRecent returns true if the babble message was posted within the last hour
|
||||
func (b *Babble) IsRecent() bool {
|
||||
return time.Since(b.PostedTime()) < time.Hour
|
||||
}
|
||||
|
||||
// Age returns how long ago the babble message was posted
|
||||
func (b *Babble) Age() time.Duration {
|
||||
return time.Since(b.PostedTime())
|
||||
}
|
||||
|
||||
// IsAuthor returns true if the given username is the author of this babble message
|
||||
func (b *Babble) IsAuthor(username string) bool {
|
||||
return strings.EqualFold(b.Author, username)
|
||||
}
|
||||
|
||||
// Preview returns a truncated version of the babble for previews
|
||||
func (b *Babble) Preview(maxLength int) string {
|
||||
if len(b.Babble) <= maxLength {
|
||||
return b.Babble
|
||||
}
|
||||
|
||||
if maxLength < 3 {
|
||||
return b.Babble[:maxLength]
|
||||
}
|
||||
|
||||
return b.Babble[:maxLength-3] + "..."
|
||||
}
|
||||
|
||||
// WordCount returns the number of words in the babble message
|
||||
func (b *Babble) WordCount() int {
|
||||
if b.Babble == "" {
|
||||
return 0
|
||||
}
|
||||
|
||||
// Simple word count by splitting on whitespace
|
||||
words := 0
|
||||
inWord := false
|
||||
|
||||
for _, char := range b.Babble {
|
||||
if char == ' ' || char == '\t' || char == '\n' || char == '\r' {
|
||||
if inWord {
|
||||
words++
|
||||
inWord = false
|
||||
}
|
||||
} else {
|
||||
inWord = true
|
||||
}
|
||||
}
|
||||
|
||||
if inWord {
|
||||
words++
|
||||
}
|
||||
|
||||
return words
|
||||
}
|
||||
|
||||
// Length returns the character length of the babble message
|
||||
func (b *Babble) Length() int {
|
||||
return len(b.Babble)
|
||||
}
|
||||
|
||||
// Contains returns true if the babble message contains the given term (case-insensitive)
|
||||
func (b *Babble) Contains(term string) bool {
|
||||
return strings.Contains(strings.ToLower(b.Babble), strings.ToLower(term))
|
||||
}
|
||||
|
||||
// IsEmpty returns true if the babble message is empty or whitespace-only
|
||||
func (b *Babble) IsEmpty() bool {
|
||||
return strings.TrimSpace(b.Babble) == ""
|
||||
}
|
||||
|
||||
// IsLongMessage returns true if the message exceeds the typical chat length
|
||||
func (b *Babble) IsLongMessage(threshold int) bool {
|
||||
return b.Length() > threshold
|
||||
}
|
||||
|
||||
// GetMentions returns a slice of usernames mentioned in the message (starting with @)
|
||||
func (b *Babble) GetMentions() []string {
|
||||
words := strings.Fields(b.Babble)
|
||||
var mentions []string
|
||||
|
||||
for _, word := range words {
|
||||
if strings.HasPrefix(word, "@") && len(word) > 1 {
|
||||
// Clean up punctuation from the end
|
||||
mention := strings.TrimRight(word[1:], ".,!?;:")
|
||||
if mention != "" {
|
||||
mentions = append(mentions, mention)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return mentions
|
||||
}
|
||||
|
||||
// HasMention returns true if the message mentions the given username
|
||||
func (b *Babble) HasMention(username string) bool {
|
||||
mentions := b.GetMentions()
|
||||
for _, mention := range mentions {
|
||||
if strings.EqualFold(mention, username) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
625
internal/babble/babble_test.go
Normal file
625
internal/babble/babble_test.go
Normal file
@ -0,0 +1,625 @@
|
||||
package babble
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"dk/internal/database"
|
||||
)
|
||||
|
||||
func setupTestDB(t *testing.T) *database.DB {
|
||||
testDB := "test_babble.db"
|
||||
t.Cleanup(func() {
|
||||
os.Remove(testDB)
|
||||
})
|
||||
|
||||
db, err := database.Open(testDB)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to open test database: %v", err)
|
||||
}
|
||||
|
||||
// Create babble table
|
||||
createTable := `CREATE TABLE babble (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
posted INTEGER NOT NULL DEFAULT (unixepoch()),
|
||||
author TEXT NOT NULL DEFAULT '',
|
||||
babble TEXT NOT NULL DEFAULT ''
|
||||
)`
|
||||
|
||||
if err := db.Exec(createTable); err != nil {
|
||||
t.Fatalf("Failed to create babble table: %v", err)
|
||||
}
|
||||
|
||||
// Insert test data with specific timestamps for predictable testing
|
||||
now := time.Now().Unix()
|
||||
testBabble := `INSERT INTO babble (posted, author, babble) VALUES
|
||||
(?, 'Alice', 'Hello everyone! Welcome to the game'),
|
||||
(?, 'Bob', 'Thanks Alice! @Alice this game is great'),
|
||||
(?, 'Charlie', 'Anyone want to team up for the dungeon?'),
|
||||
(?, 'Alice', 'I can help @Charlie, let me know'),
|
||||
(?, 'David', 'Server lag is really bad right now...'),
|
||||
(?, 'Eve', 'Quick question about spell mechanics')`
|
||||
|
||||
timestamps := []interface{}{
|
||||
now - 3600*6, // 6 hours ago
|
||||
now - 3600*4, // 4 hours ago
|
||||
now - 3600*2, // 2 hours ago
|
||||
now - 3600*1, // 1 hour ago
|
||||
now - 1800, // 30 minutes ago
|
||||
now - 300, // 5 minutes ago
|
||||
}
|
||||
|
||||
if err := db.Exec(testBabble, timestamps...); err != nil {
|
||||
t.Fatalf("Failed to insert test babble: %v", err)
|
||||
}
|
||||
|
||||
return db
|
||||
}
|
||||
|
||||
func TestFind(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer db.Close()
|
||||
|
||||
// Test finding existing babble
|
||||
babble, err := Find(db, 1)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to find babble: %v", err)
|
||||
}
|
||||
|
||||
if babble.ID != 1 {
|
||||
t.Errorf("Expected ID 1, got %d", babble.ID)
|
||||
}
|
||||
if babble.Author != "Alice" {
|
||||
t.Errorf("Expected author 'Alice', got '%s'", babble.Author)
|
||||
}
|
||||
if babble.Babble != "Hello everyone! Welcome to the game" {
|
||||
t.Errorf("Expected specific message, got '%s'", babble.Babble)
|
||||
}
|
||||
if babble.Posted == 0 {
|
||||
t.Error("Expected non-zero posted timestamp")
|
||||
}
|
||||
|
||||
// Test finding non-existent babble
|
||||
_, err = Find(db, 999)
|
||||
if err == nil {
|
||||
t.Error("Expected error when finding non-existent babble")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAll(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer db.Close()
|
||||
|
||||
babbles, err := All(db)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get all babble: %v", err)
|
||||
}
|
||||
|
||||
if len(babbles) != 6 {
|
||||
t.Errorf("Expected 6 babble messages, got %d", len(babbles))
|
||||
}
|
||||
|
||||
// Check ordering (newest first)
|
||||
if len(babbles) >= 2 {
|
||||
if babbles[0].Posted < babbles[1].Posted {
|
||||
t.Error("Expected babble to be ordered by posted time (newest first)")
|
||||
}
|
||||
}
|
||||
|
||||
// First message should be the most recent (5 minutes ago)
|
||||
if babbles[0].Author != "Eve" {
|
||||
t.Errorf("Expected newest message from Eve, got from '%s'", babbles[0].Author)
|
||||
}
|
||||
}
|
||||
|
||||
func TestByAuthor(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer db.Close()
|
||||
|
||||
// Test messages by Alice
|
||||
aliceMessages, err := ByAuthor(db, "Alice")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get babble by author: %v", err)
|
||||
}
|
||||
|
||||
if len(aliceMessages) != 2 {
|
||||
t.Errorf("Expected 2 messages by Alice, got %d", len(aliceMessages))
|
||||
}
|
||||
|
||||
// Verify all messages are by Alice
|
||||
for _, message := range aliceMessages {
|
||||
if message.Author != "Alice" {
|
||||
t.Errorf("Expected author 'Alice', got '%s'", message.Author)
|
||||
}
|
||||
}
|
||||
|
||||
// Check ordering (newest first)
|
||||
if len(aliceMessages) == 2 {
|
||||
if aliceMessages[0].Babble != "I can help @Charlie, let me know" {
|
||||
t.Errorf("Expected newest message by Alice first")
|
||||
}
|
||||
}
|
||||
|
||||
// Test case insensitive search
|
||||
aliceMessagesLower, err := ByAuthor(db, "alice")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get babble by lowercase author: %v", err)
|
||||
}
|
||||
|
||||
if len(aliceMessagesLower) != 2 {
|
||||
t.Errorf("Expected case insensitive search to find 2 messages, got %d", len(aliceMessagesLower))
|
||||
}
|
||||
|
||||
// Test author with no messages
|
||||
noMessages, err := ByAuthor(db, "NonexistentUser")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to query non-existent author: %v", err)
|
||||
}
|
||||
|
||||
if len(noMessages) != 0 {
|
||||
t.Errorf("Expected 0 messages by non-existent author, got %d", len(noMessages))
|
||||
}
|
||||
}
|
||||
|
||||
func TestRecent(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer db.Close()
|
||||
|
||||
// Test getting 3 most recent messages
|
||||
recentMessages, err := Recent(db, 3)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get recent babble: %v", err)
|
||||
}
|
||||
|
||||
if len(recentMessages) != 3 {
|
||||
t.Errorf("Expected 3 recent messages, got %d", len(recentMessages))
|
||||
}
|
||||
|
||||
// Check ordering (newest first)
|
||||
if len(recentMessages) >= 2 {
|
||||
if recentMessages[0].Posted < recentMessages[1].Posted {
|
||||
t.Error("Expected recent messages to be ordered newest first")
|
||||
}
|
||||
}
|
||||
|
||||
// Test getting more messages than exist
|
||||
allRecentMessages, err := Recent(db, 10)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get recent babble with high limit: %v", err)
|
||||
}
|
||||
|
||||
if len(allRecentMessages) != 6 {
|
||||
t.Errorf("Expected 6 messages (all available), got %d", len(allRecentMessages))
|
||||
}
|
||||
}
|
||||
|
||||
func TestSince(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer db.Close()
|
||||
|
||||
// Test messages since 3 hours ago
|
||||
threeHoursAgo := time.Now().Add(-3 * time.Hour).Unix()
|
||||
recentMessages, err := Since(db, threeHoursAgo)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get babble since timestamp: %v", err)
|
||||
}
|
||||
|
||||
// Should get messages from 2 hours ago, 1 hour ago, 30 minutes ago, and 5 minutes ago
|
||||
expectedCount := 4
|
||||
if len(recentMessages) != expectedCount {
|
||||
t.Errorf("Expected %d messages since 3 hours ago, got %d", expectedCount, len(recentMessages))
|
||||
}
|
||||
|
||||
// Verify all messages are since the timestamp
|
||||
for _, message := range recentMessages {
|
||||
if message.Posted < threeHoursAgo {
|
||||
t.Errorf("Message with timestamp %d is before the 'since' timestamp %d", message.Posted, threeHoursAgo)
|
||||
}
|
||||
}
|
||||
|
||||
// Test with future timestamp (should return no messages)
|
||||
futureMessages, err := Since(db, time.Now().Add(time.Hour).Unix())
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to query future timestamp: %v", err)
|
||||
}
|
||||
|
||||
if len(futureMessages) != 0 {
|
||||
t.Errorf("Expected 0 messages since future timestamp, got %d", len(futureMessages))
|
||||
}
|
||||
}
|
||||
|
||||
func TestBetween(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer db.Close()
|
||||
|
||||
// Test messages between 5 hours ago and 1 hour ago
|
||||
start := time.Now().Add(-5 * time.Hour).Unix()
|
||||
end := time.Now().Add(-1 * time.Hour).Unix()
|
||||
|
||||
betweenMessages, err := Between(db, start, end)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get babble between timestamps: %v", err)
|
||||
}
|
||||
|
||||
// Should get messages from 4 hours ago, 2 hours ago, and 1 hour ago (inclusive end)
|
||||
expectedCount := 3
|
||||
if len(betweenMessages) != expectedCount {
|
||||
t.Errorf("Expected %d messages between timestamps, got %d", expectedCount, len(betweenMessages))
|
||||
}
|
||||
|
||||
// Verify all messages are within the range
|
||||
for _, message := range betweenMessages {
|
||||
if message.Posted < start || message.Posted > end {
|
||||
t.Errorf("Message with timestamp %d is outside range [%d, %d]", message.Posted, start, end)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSearch(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer db.Close()
|
||||
|
||||
// Test searching for "game"
|
||||
gameMessages, err := Search(db, "game")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to search babble: %v", err)
|
||||
}
|
||||
|
||||
expectedCount := 2 // Alice's welcome message and Bob's response
|
||||
if len(gameMessages) != expectedCount {
|
||||
t.Errorf("Expected %d messages containing 'game', got %d", expectedCount, len(gameMessages))
|
||||
}
|
||||
|
||||
// Verify all messages contain the search term
|
||||
for _, message := range gameMessages {
|
||||
if !message.Contains("game") {
|
||||
t.Errorf("Message '%s' does not contain search term 'game'", message.Babble)
|
||||
}
|
||||
}
|
||||
|
||||
// Test case insensitive search
|
||||
gameMessagesUpper, err := Search(db, "GAME")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to search babble with uppercase: %v", err)
|
||||
}
|
||||
|
||||
if len(gameMessagesUpper) != expectedCount {
|
||||
t.Error("Expected case insensitive search to find same results")
|
||||
}
|
||||
|
||||
// Test search with no results
|
||||
noResults, err := Search(db, "nonexistentterm")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to search for non-existent term: %v", err)
|
||||
}
|
||||
|
||||
if len(noResults) != 0 {
|
||||
t.Errorf("Expected 0 results for non-existent term, got %d", len(noResults))
|
||||
}
|
||||
}
|
||||
|
||||
func TestRecentByAuthor(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer db.Close()
|
||||
|
||||
// Test recent messages by Alice (limit 1)
|
||||
aliceRecent, err := RecentByAuthor(db, "Alice", 1)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get recent babble by author: %v", err)
|
||||
}
|
||||
|
||||
if len(aliceRecent) != 1 {
|
||||
t.Errorf("Expected 1 recent message by Alice, got %d", len(aliceRecent))
|
||||
}
|
||||
|
||||
if len(aliceRecent) > 0 && aliceRecent[0].Babble != "I can help @Charlie, let me know" {
|
||||
t.Error("Expected most recent message by Alice")
|
||||
}
|
||||
|
||||
// Test with higher limit
|
||||
aliceAll, err := RecentByAuthor(db, "Alice", 5)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get all recent messages by Alice: %v", err)
|
||||
}
|
||||
|
||||
if len(aliceAll) != 2 {
|
||||
t.Errorf("Expected 2 total messages by Alice, got %d", len(aliceAll))
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilder(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer db.Close()
|
||||
|
||||
// Create new babble using builder
|
||||
testTime := time.Now()
|
||||
babble, err := NewBuilder(db).
|
||||
WithAuthor("TestUser").
|
||||
WithBabble("Test message from builder").
|
||||
WithPostedTime(testTime).
|
||||
Create()
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create babble with builder: %v", err)
|
||||
}
|
||||
|
||||
if babble.ID == 0 {
|
||||
t.Error("Expected non-zero ID after creation")
|
||||
}
|
||||
if babble.Author != "TestUser" {
|
||||
t.Errorf("Expected author 'TestUser', got '%s'", babble.Author)
|
||||
}
|
||||
if babble.Babble != "Test message from builder" {
|
||||
t.Errorf("Expected specific message, got '%s'", babble.Babble)
|
||||
}
|
||||
if babble.Posted != testTime.Unix() {
|
||||
t.Errorf("Expected posted time %d, got %d", testTime.Unix(), babble.Posted)
|
||||
}
|
||||
|
||||
// Test WithMessage alias
|
||||
babble2, err := NewBuilder(db).
|
||||
WithAuthor("TestUser2").
|
||||
WithMessage("Using WithMessage alias").
|
||||
Create()
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create babble with WithMessage: %v", err)
|
||||
}
|
||||
|
||||
if babble2.Babble != "Using WithMessage alias" {
|
||||
t.Errorf("WithMessage alias failed, got '%s'", babble2.Babble)
|
||||
}
|
||||
|
||||
// Verify it was saved to database
|
||||
foundBabble, err := Find(db, babble.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to find created babble: %v", err)
|
||||
}
|
||||
|
||||
if foundBabble.Babble != "Test message from builder" {
|
||||
t.Errorf("Created babble not found in database")
|
||||
}
|
||||
|
||||
// Test builder with default timestamp
|
||||
defaultBabble, err := NewBuilder(db).
|
||||
WithAuthor("DefaultUser").
|
||||
WithBabble("Message with default timestamp").
|
||||
Create()
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create babble with default timestamp: %v", err)
|
||||
}
|
||||
|
||||
// Should have recent timestamp (within last minute)
|
||||
if time.Since(defaultBabble.PostedTime()) > time.Minute {
|
||||
t.Error("Expected default timestamp to be recent")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSave(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer db.Close()
|
||||
|
||||
babble, err := Find(db, 1)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to find babble: %v", err)
|
||||
}
|
||||
|
||||
// Modify babble
|
||||
babble.Author = "UpdatedAuthor"
|
||||
babble.Babble = "Updated message content"
|
||||
babble.Posted = time.Now().Unix()
|
||||
|
||||
// Save changes
|
||||
err = babble.Save()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to save babble: %v", err)
|
||||
}
|
||||
|
||||
// Verify changes were saved
|
||||
updatedBabble, err := Find(db, 1)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to find updated babble: %v", err)
|
||||
}
|
||||
|
||||
if updatedBabble.Author != "UpdatedAuthor" {
|
||||
t.Errorf("Expected updated author 'UpdatedAuthor', got '%s'", updatedBabble.Author)
|
||||
}
|
||||
if updatedBabble.Babble != "Updated message content" {
|
||||
t.Errorf("Expected updated message, got '%s'", updatedBabble.Babble)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDelete(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer db.Close()
|
||||
|
||||
babble, err := Find(db, 1)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to find babble: %v", err)
|
||||
}
|
||||
|
||||
// Delete babble
|
||||
err = babble.Delete()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to delete babble: %v", err)
|
||||
}
|
||||
|
||||
// Verify babble was deleted
|
||||
_, err = Find(db, 1)
|
||||
if err == nil {
|
||||
t.Error("Expected error when finding deleted babble")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUtilityMethods(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer db.Close()
|
||||
|
||||
babble, _ := Find(db, 1)
|
||||
|
||||
// Test PostedTime
|
||||
postedTime := babble.PostedTime()
|
||||
if postedTime.IsZero() {
|
||||
t.Error("Expected non-zero posted time")
|
||||
}
|
||||
|
||||
// Test SetPostedTime
|
||||
newTime := time.Now().Add(-30 * time.Minute)
|
||||
babble.SetPostedTime(newTime)
|
||||
if babble.Posted != newTime.Unix() {
|
||||
t.Errorf("Expected posted timestamp %d, got %d", newTime.Unix(), babble.Posted)
|
||||
}
|
||||
|
||||
// Test IsRecent (should be true for 30 minutes ago)
|
||||
if !babble.IsRecent() {
|
||||
t.Error("Expected message from 30 minutes ago to be recent")
|
||||
}
|
||||
|
||||
// Test Age
|
||||
age := babble.Age()
|
||||
if age < 0 {
|
||||
t.Error("Expected positive age")
|
||||
}
|
||||
|
||||
// Test IsAuthor
|
||||
if !babble.IsAuthor("Alice") {
|
||||
t.Error("Expected IsAuthor to return true for correct author")
|
||||
}
|
||||
if !babble.IsAuthor("alice") { // Test case insensitive
|
||||
t.Error("Expected IsAuthor to be case insensitive")
|
||||
}
|
||||
if babble.IsAuthor("Bob") {
|
||||
t.Error("Expected IsAuthor to return false for incorrect author")
|
||||
}
|
||||
|
||||
// Test Preview
|
||||
longMessage := "This is a very long chat message that should be truncated when preview is called for display purposes"
|
||||
babble.Babble = longMessage
|
||||
|
||||
preview := babble.Preview(20)
|
||||
if len(preview) > 20 {
|
||||
t.Errorf("Expected preview length <= 20, got %d", len(preview))
|
||||
}
|
||||
if preview[len(preview)-3:] != "..." {
|
||||
t.Error("Expected preview to end with ellipsis")
|
||||
}
|
||||
|
||||
shortPreview := babble.Preview(200) // Longer than message
|
||||
if shortPreview != longMessage {
|
||||
t.Error("Expected short message to not be truncated")
|
||||
}
|
||||
|
||||
// Test WordCount
|
||||
babble.Babble = "This is a test with five words"
|
||||
wordCount := babble.WordCount()
|
||||
if wordCount != 7 {
|
||||
t.Errorf("Expected 7 words, got %d", wordCount)
|
||||
}
|
||||
|
||||
// Test Length
|
||||
expectedLength := len(babble.Babble)
|
||||
if babble.Length() != expectedLength {
|
||||
t.Errorf("Expected length %d, got %d", expectedLength, babble.Length())
|
||||
}
|
||||
|
||||
// Test Contains
|
||||
if !babble.Contains("test") {
|
||||
t.Error("Expected message to contain 'test'")
|
||||
}
|
||||
if !babble.Contains("TEST") { // Case insensitive
|
||||
t.Error("Expected Contains to be case insensitive")
|
||||
}
|
||||
if babble.Contains("nonexistent") {
|
||||
t.Error("Expected message not to contain 'nonexistent'")
|
||||
}
|
||||
|
||||
// Test IsEmpty
|
||||
babble.Babble = ""
|
||||
if !babble.IsEmpty() {
|
||||
t.Error("Expected empty message to be empty")
|
||||
}
|
||||
|
||||
babble.Babble = " "
|
||||
if !babble.IsEmpty() {
|
||||
t.Error("Expected whitespace-only message to be empty")
|
||||
}
|
||||
|
||||
babble.Babble = "Not empty"
|
||||
if babble.IsEmpty() {
|
||||
t.Error("Expected non-empty message not to be empty")
|
||||
}
|
||||
|
||||
// Test IsLongMessage
|
||||
shortMsg := "Short"
|
||||
babble.Babble = shortMsg
|
||||
if babble.IsLongMessage(100) {
|
||||
t.Error("Expected short message not to be long")
|
||||
}
|
||||
if !babble.IsLongMessage(3) {
|
||||
t.Error("Expected message longer than threshold to be long")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMentionMethods(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer db.Close()
|
||||
|
||||
// Test GetMentions
|
||||
babble, _ := Find(db, 2) // Bob's message: "Thanks Alice! @Alice this game is great"
|
||||
mentions := babble.GetMentions()
|
||||
|
||||
expectedMentions := []string{"Alice"}
|
||||
if len(mentions) != len(expectedMentions) {
|
||||
t.Errorf("Expected %d mentions, got %d", len(expectedMentions), len(mentions))
|
||||
}
|
||||
|
||||
for i, expected := range expectedMentions {
|
||||
if i < len(mentions) && mentions[i] != expected {
|
||||
t.Errorf("Expected mention '%s' at position %d, got '%s'", expected, i, mentions[i])
|
||||
}
|
||||
}
|
||||
|
||||
// Test HasMention
|
||||
if !babble.HasMention("Alice") {
|
||||
t.Error("Expected message to mention Alice")
|
||||
}
|
||||
if !babble.HasMention("alice") { // Case insensitive
|
||||
t.Error("Expected HasMention to be case insensitive")
|
||||
}
|
||||
if babble.HasMention("Bob") {
|
||||
t.Error("Expected message not to mention Bob")
|
||||
}
|
||||
|
||||
// Test message with multiple mentions and punctuation
|
||||
babble.Babble = "Hey @Alice, @Bob! Can you help @Charlie?"
|
||||
mentions = babble.GetMentions()
|
||||
expectedMentions = []string{"Alice", "Bob", "Charlie"}
|
||||
|
||||
if len(mentions) != len(expectedMentions) {
|
||||
t.Errorf("Expected %d mentions, got %d: %v", len(expectedMentions), len(mentions), mentions)
|
||||
}
|
||||
|
||||
for _, expected := range expectedMentions {
|
||||
if !babble.HasMention(expected) {
|
||||
t.Errorf("Expected message to mention %s", expected)
|
||||
}
|
||||
}
|
||||
|
||||
// Test message with no mentions
|
||||
babble.Babble = "No mentions in this message"
|
||||
mentions = babble.GetMentions()
|
||||
|
||||
if len(mentions) != 0 {
|
||||
t.Errorf("Expected 0 mentions, got %d", len(mentions))
|
||||
}
|
||||
|
||||
// Test malformed mentions (should be ignored)
|
||||
babble.Babble = "Just @ alone or @"
|
||||
mentions = babble.GetMentions()
|
||||
|
||||
if len(mentions) != 0 {
|
||||
t.Errorf("Expected 0 mentions for malformed @, got %d", len(mentions))
|
||||
}
|
||||
}
|
90
internal/babble/builder.go
Normal file
90
internal/babble/builder.go
Normal file
@ -0,0 +1,90 @@
|
||||
package babble
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"dk/internal/database"
|
||||
|
||||
"zombiezen.com/go/sqlite"
|
||||
)
|
||||
|
||||
// Builder provides a fluent interface for creating babble messages
|
||||
type Builder struct {
|
||||
babble *Babble
|
||||
db *database.DB
|
||||
}
|
||||
|
||||
// NewBuilder creates a new babble builder
|
||||
func NewBuilder(db *database.DB) *Builder {
|
||||
return &Builder{
|
||||
babble: &Babble{
|
||||
db: db,
|
||||
Posted: time.Now().Unix(), // Default to current time
|
||||
},
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
|
||||
// WithAuthor sets the author username
|
||||
func (b *Builder) WithAuthor(author string) *Builder {
|
||||
b.babble.Author = author
|
||||
return b
|
||||
}
|
||||
|
||||
// WithBabble sets the message content
|
||||
func (b *Builder) WithBabble(message string) *Builder {
|
||||
b.babble.Babble = message
|
||||
return b
|
||||
}
|
||||
|
||||
// WithMessage is an alias for WithBabble for more intuitive usage
|
||||
func (b *Builder) WithMessage(message string) *Builder {
|
||||
return b.WithBabble(message)
|
||||
}
|
||||
|
||||
// WithPosted sets the posted timestamp
|
||||
func (b *Builder) WithPosted(posted int64) *Builder {
|
||||
b.babble.Posted = posted
|
||||
return b
|
||||
}
|
||||
|
||||
// WithPostedTime sets the posted timestamp from a time.Time
|
||||
func (b *Builder) WithPostedTime(t time.Time) *Builder {
|
||||
b.babble.Posted = t.Unix()
|
||||
return b
|
||||
}
|
||||
|
||||
// Create saves the babble message to the database and returns the created babble with ID
|
||||
func (b *Builder) Create() (*Babble, error) {
|
||||
// Use a transaction to ensure we can get the ID
|
||||
var babble *Babble
|
||||
err := b.db.Transaction(func(tx *database.Tx) error {
|
||||
query := `INSERT INTO babble (posted, author, babble)
|
||||
VALUES (?, ?, ?)`
|
||||
|
||||
if err := tx.Exec(query, b.babble.Posted, b.babble.Author, b.babble.Babble); err != nil {
|
||||
return fmt.Errorf("failed to insert babble: %w", err)
|
||||
}
|
||||
|
||||
// Get the last insert ID
|
||||
var id int
|
||||
err := tx.Query("SELECT last_insert_rowid()", func(stmt *sqlite.Stmt) error {
|
||||
id = stmt.ColumnInt(0)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get insert ID: %w", err)
|
||||
}
|
||||
|
||||
b.babble.ID = id
|
||||
babble = b.babble
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return babble, nil
|
||||
}
|
514
internal/babble/doc.go
Normal file
514
internal/babble/doc.go
Normal file
@ -0,0 +1,514 @@
|
||||
/*
|
||||
Package babble is the active record implementation for global chat messages in the game.
|
||||
|
||||
Babble represents the global chat system where players can communicate with each other in real-time. The package provides comprehensive chat message management with features like mentions, search, time-based queries, and moderation utilities.
|
||||
|
||||
# Basic Usage
|
||||
|
||||
To retrieve a babble message by ID:
|
||||
|
||||
message, err := babble.Find(db, 1)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Printf("<%s> %s\n", message.Author, message.Babble)
|
||||
|
||||
To get all babble messages:
|
||||
|
||||
allMessages, err := babble.All(db)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
for _, message := range allMessages {
|
||||
fmt.Printf("[%s] <%s> %s\n",
|
||||
message.PostedTime().Format("15:04"),
|
||||
message.Author,
|
||||
message.Babble)
|
||||
}
|
||||
|
||||
To get recent chat messages:
|
||||
|
||||
recentChat, err := babble.Recent(db, 50)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
To filter messages by author:
|
||||
|
||||
userMessages, err := babble.ByAuthor(db, "PlayerName")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
# Creating Messages with Builder Pattern
|
||||
|
||||
The package provides a fluent builder interface for creating new chat messages:
|
||||
|
||||
message, err := babble.NewBuilder(db).
|
||||
WithAuthor("PlayerName").
|
||||
WithBabble("Hello everyone! Ready for some adventure?").
|
||||
WithPostedTime(time.Now()).
|
||||
Create()
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Printf("Posted message with ID: %d\n", message.ID)
|
||||
|
||||
The builder automatically sets the current time if no posted time is specified:
|
||||
|
||||
message, err := babble.NewBuilder(db).
|
||||
WithAuthor("Admin").
|
||||
WithMessage("Server restart in 5 minutes!").
|
||||
Create() // Uses current timestamp
|
||||
|
||||
You can use either `WithBabble()` or `WithMessage()` - they are aliases for the same functionality.
|
||||
|
||||
# Updating Messages
|
||||
|
||||
Chat messages can be modified and saved back to the database:
|
||||
|
||||
message, _ := babble.Find(db, 1)
|
||||
message.Babble = "[EDITED] Original message was inappropriate"
|
||||
|
||||
err := message.Save()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
# Deleting Messages
|
||||
|
||||
Messages can be removed from the database (for moderation):
|
||||
|
||||
message, _ := babble.Find(db, 1)
|
||||
err := message.Delete()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
# Database Schema
|
||||
|
||||
The babble table has the following structure:
|
||||
|
||||
CREATE TABLE babble (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
posted INTEGER NOT NULL DEFAULT (unixepoch()),
|
||||
author TEXT NOT NULL DEFAULT '',
|
||||
babble TEXT NOT NULL DEFAULT ''
|
||||
)
|
||||
|
||||
Where:
|
||||
- id: Unique identifier for the chat message
|
||||
- posted: Unix timestamp when the message was posted
|
||||
- author: Username of the player who posted the message
|
||||
- babble: The text content of the chat message
|
||||
|
||||
# Time-Based Queries
|
||||
|
||||
## Recent Messages
|
||||
|
||||
Get the most recent chat messages:
|
||||
|
||||
// Get 100 most recent messages for chat display
|
||||
chatHistory, err := babble.Recent(db, 100)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
fmt.Println("=== Recent Chat ===")
|
||||
for _, msg := range chatHistory {
|
||||
age := msg.Age()
|
||||
timeStr := ""
|
||||
if age < time.Minute {
|
||||
timeStr = "just now"
|
||||
} else if age < time.Hour {
|
||||
timeStr = fmt.Sprintf("%dm ago", int(age.Minutes()))
|
||||
} else {
|
||||
timeStr = msg.PostedTime().Format("15:04")
|
||||
}
|
||||
|
||||
fmt.Printf("[%s] <%s> %s\n", timeStr, msg.Author, msg.Babble)
|
||||
}
|
||||
|
||||
## Messages Since Timestamp
|
||||
|
||||
Get messages posted since a specific time:
|
||||
|
||||
// Get messages since user's last login
|
||||
lastLogin := getUserLastLogin(userID)
|
||||
newMessages, err := babble.Since(db, lastLogin)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if len(newMessages) > 0 {
|
||||
fmt.Printf("You missed %d messages while you were away\n", len(newMessages))
|
||||
}
|
||||
|
||||
## Messages Between Timestamps
|
||||
|
||||
Get messages within a time range:
|
||||
|
||||
// Get today's chat history
|
||||
startOfDay := time.Now().Truncate(24 * time.Hour).Unix()
|
||||
endOfDay := time.Now().Unix()
|
||||
|
||||
todaysChat, err := babble.Between(db, startOfDay, endOfDay)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
# Search and Filtering
|
||||
|
||||
## Text Search
|
||||
|
||||
Search for messages containing specific terms:
|
||||
|
||||
// Search for messages about "boss fight"
|
||||
bossMessages, err := babble.Search(db, "boss fight")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
for _, msg := range bossMessages {
|
||||
fmt.Printf("<%s> %s\n", msg.Author, msg.Preview(60))
|
||||
}
|
||||
|
||||
Search is case-insensitive and matches partial words.
|
||||
|
||||
## Author-Based Queries
|
||||
|
||||
Get messages from specific players:
|
||||
|
||||
// Get recent messages from a player
|
||||
playerRecent, err := babble.RecentByAuthor(db, "PlayerName", 10)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Get all messages from a player (for moderation)
|
||||
allPlayerMessages, err := babble.ByAuthor(db, "ReportedPlayer")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
All author searches are case-insensitive.
|
||||
|
||||
# Mention System
|
||||
|
||||
## Finding Mentions
|
||||
|
||||
The package includes a comprehensive mention system using @username syntax:
|
||||
|
||||
message, _ := babble.Find(db, someID)
|
||||
|
||||
// Get all mentioned usernames
|
||||
mentions := message.GetMentions()
|
||||
for _, username := range mentions {
|
||||
fmt.Printf("Message mentions: @%s\n", username)
|
||||
}
|
||||
|
||||
// Check if specific user is mentioned
|
||||
if message.HasMention("PlayerName") {
|
||||
fmt.Println("You were mentioned in this message!")
|
||||
}
|
||||
|
||||
## Mention Parsing
|
||||
|
||||
The mention system handles various formats:
|
||||
- `@username` - Basic mention
|
||||
- `@username!` - With punctuation
|
||||
- `@username,` - In lists
|
||||
- `@username?` - In questions
|
||||
|
||||
Mentions are extracted without the punctuation and are case-insensitive.
|
||||
|
||||
## Notification Integration
|
||||
|
||||
Use mentions for player notifications:
|
||||
|
||||
// Process new messages for mentions
|
||||
recentMessages, _ := babble.Recent(db, 50)
|
||||
|
||||
for _, msg := range recentMessages {
|
||||
mentions := msg.GetMentions()
|
||||
for _, mentionedUser := range mentions {
|
||||
// Send notification to mentioned user
|
||||
notifyUser(mentionedUser, fmt.Sprintf("%s mentioned you: %s",
|
||||
msg.Author, msg.Preview(50)))
|
||||
}
|
||||
}
|
||||
|
||||
# Message Analysis
|
||||
|
||||
## Content Analysis
|
||||
|
||||
Analyze message content for moderation or statistics:
|
||||
|
||||
message, _ := babble.Find(db, someID)
|
||||
|
||||
// Basic content metrics
|
||||
fmt.Printf("Length: %d characters\n", message.Length())
|
||||
fmt.Printf("Words: %d\n", message.WordCount())
|
||||
|
||||
// Content checks
|
||||
if message.IsEmpty() {
|
||||
fmt.Println("Empty message detected")
|
||||
}
|
||||
|
||||
if message.IsLongMessage(200) {
|
||||
fmt.Println("Very long message - possible spam")
|
||||
}
|
||||
|
||||
// Search within message
|
||||
if message.Contains("inappropriate_term") {
|
||||
fmt.Println("Message flagged for review")
|
||||
}
|
||||
|
||||
## Time Analysis
|
||||
|
||||
Analyze posting patterns:
|
||||
|
||||
message, _ := babble.Find(db, someID)
|
||||
|
||||
age := message.Age()
|
||||
fmt.Printf("Message posted %v ago\n", age)
|
||||
|
||||
if message.IsRecent() {
|
||||
fmt.Println("This is a recent message (within 1 hour)")
|
||||
}
|
||||
|
||||
// Format for display
|
||||
postedTime := message.PostedTime()
|
||||
if age < 24*time.Hour {
|
||||
fmt.Printf("Posted at %s\n", postedTime.Format("15:04"))
|
||||
} else {
|
||||
fmt.Printf("Posted on %s\n", postedTime.Format("Jan 2 15:04"))
|
||||
}
|
||||
|
||||
# Chat Display Patterns
|
||||
|
||||
## Live Chat Feed
|
||||
|
||||
Display real-time chat messages:
|
||||
|
||||
func displayChatFeed(db *database.DB) {
|
||||
messages, _ := babble.Recent(db, 50)
|
||||
|
||||
fmt.Println("=== Global Chat ===")
|
||||
for i := len(messages) - 1; i >= 0; i-- { // Reverse for chronological order
|
||||
msg := messages[i]
|
||||
|
||||
// Format timestamp
|
||||
age := msg.Age()
|
||||
var timeStr string
|
||||
if age < time.Minute {
|
||||
timeStr = "now"
|
||||
} else if age < time.Hour {
|
||||
timeStr = fmt.Sprintf("%dm", int(age.Minutes()))
|
||||
} else {
|
||||
timeStr = msg.PostedTime().Format("15:04")
|
||||
}
|
||||
|
||||
// Handle mentions highlighting
|
||||
content := msg.Babble
|
||||
if msg.HasMention(currentUser) {
|
||||
content = fmt.Sprintf("🔔 %s", content) // Highlight mentions
|
||||
}
|
||||
|
||||
fmt.Printf("[%s] <%s> %s\n", timeStr, msg.Author, content)
|
||||
}
|
||||
}
|
||||
|
||||
## Chat History Browser
|
||||
|
||||
Browse historical messages:
|
||||
|
||||
func browseChatHistory(db *database.DB, page int, pageSize int) {
|
||||
offset := page * pageSize
|
||||
|
||||
// Get paginated results (implement with LIMIT/OFFSET)
|
||||
allMessages, _ := babble.All(db)
|
||||
|
||||
start := offset
|
||||
end := offset + pageSize
|
||||
if end > len(allMessages) {
|
||||
end = len(allMessages)
|
||||
}
|
||||
|
||||
if start >= len(allMessages) {
|
||||
fmt.Println("No more messages")
|
||||
return
|
||||
}
|
||||
|
||||
pageMessages := allMessages[start:end]
|
||||
|
||||
fmt.Printf("=== Chat History (Page %d) ===\n", page+1)
|
||||
for _, msg := range pageMessages {
|
||||
fmt.Printf("%s <%s> %s\n",
|
||||
msg.PostedTime().Format("Jan 2 15:04"),
|
||||
msg.Author,
|
||||
msg.Babble)
|
||||
}
|
||||
}
|
||||
|
||||
# Moderation Features
|
||||
|
||||
## Content Moderation
|
||||
|
||||
Tools for chat moderation:
|
||||
|
||||
// Flag inappropriate messages
|
||||
func moderateMessage(db *database.DB, messageID int) {
|
||||
msg, err := babble.Find(db, messageID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Check for spam (very short or very long)
|
||||
if msg.WordCount() < 2 {
|
||||
fmt.Printf("Possible spam: %s\n", msg.Preview(30))
|
||||
}
|
||||
|
||||
if msg.IsLongMessage(500) {
|
||||
fmt.Printf("Very long message from %s\n", msg.Author)
|
||||
}
|
||||
|
||||
// Check for excessive mentions
|
||||
mentions := msg.GetMentions()
|
||||
if len(mentions) > 5 {
|
||||
fmt.Printf("Message with %d mentions from %s\n", len(mentions), msg.Author)
|
||||
}
|
||||
}
|
||||
|
||||
## User Activity Analysis
|
||||
|
||||
Analyze user posting patterns:
|
||||
|
||||
// Check user activity
|
||||
func analyzeUserActivity(db *database.DB, username string) {
|
||||
// Recent activity
|
||||
recentMessages, _ := babble.RecentByAuthor(db, username, 10)
|
||||
|
||||
fmt.Printf("User %s recent activity:\n", username)
|
||||
fmt.Printf("- Recent messages: %d\n", len(recentMessages))
|
||||
|
||||
if len(recentMessages) > 0 {
|
||||
totalWords := 0
|
||||
for _, msg := range recentMessages {
|
||||
totalWords += msg.WordCount()
|
||||
}
|
||||
avgWords := totalWords / len(recentMessages)
|
||||
fmt.Printf("- Average words per message: %d\n", avgWords)
|
||||
|
||||
latest := recentMessages[0]
|
||||
fmt.Printf("- Last message: %s (%s ago)\n",
|
||||
latest.Preview(40), latest.Age())
|
||||
}
|
||||
|
||||
// Check for mention patterns
|
||||
allUserMessages, _ := babble.ByAuthor(db, username)
|
||||
mentionCount := 0
|
||||
for _, msg := range allUserMessages {
|
||||
mentionCount += len(msg.GetMentions())
|
||||
}
|
||||
|
||||
if len(allUserMessages) > 0 {
|
||||
avgMentions := float64(mentionCount) / float64(len(allUserMessages))
|
||||
fmt.Printf("- Average mentions per message: %.2f\n", avgMentions)
|
||||
}
|
||||
}
|
||||
|
||||
# Performance Considerations
|
||||
|
||||
## Efficient Queries
|
||||
|
||||
All time-based queries are optimized:
|
||||
|
||||
// Recent messages are efficiently ordered
|
||||
recent, _ := babble.Recent(db, 100) // Uses LIMIT for efficiency
|
||||
|
||||
// Time-based queries use indexed timestamp
|
||||
since, _ := babble.Since(db, timestamp) // Efficient with proper index
|
||||
|
||||
// Author queries support case-insensitive search
|
||||
authorMessages, _ := babble.ByAuthor(db, "username") // Uses LOWER() function
|
||||
|
||||
## Memory Management
|
||||
|
||||
For large chat histories, process in batches:
|
||||
|
||||
// Process messages in batches
|
||||
func processAllMessages(db *database.DB, batchSize int) {
|
||||
allMessages, _ := babble.All(db)
|
||||
|
||||
for i := 0; i < len(allMessages); i += batchSize {
|
||||
end := i + batchSize
|
||||
if end > len(allMessages) {
|
||||
end = len(allMessages)
|
||||
}
|
||||
|
||||
batch := allMessages[i:end]
|
||||
processBatch(batch)
|
||||
}
|
||||
}
|
||||
|
||||
# Integration Examples
|
||||
|
||||
## Real-Time Chat
|
||||
|
||||
Implement live chat updates:
|
||||
|
||||
func pollForNewMessages(db *database.DB, lastMessageID int) []*babble.Babble {
|
||||
// Get messages newer than last seen
|
||||
allMessages, _ := babble.All(db)
|
||||
|
||||
newMessages := make([]*babble.Babble, 0)
|
||||
for _, msg := range allMessages {
|
||||
if msg.ID > lastMessageID {
|
||||
newMessages = append(newMessages, msg)
|
||||
}
|
||||
}
|
||||
|
||||
return newMessages
|
||||
}
|
||||
|
||||
## Chat Commands
|
||||
|
||||
Process special chat commands:
|
||||
|
||||
func processMessage(db *database.DB, author, content string) {
|
||||
// Check for commands
|
||||
if strings.HasPrefix(content, "/") {
|
||||
handleCommand(author, content)
|
||||
return
|
||||
}
|
||||
|
||||
// Regular chat message
|
||||
msg, err := babble.NewBuilder(db).
|
||||
WithAuthor(author).
|
||||
WithBabble(content).
|
||||
Create()
|
||||
|
||||
if err != nil {
|
||||
log.Printf("Failed to save message: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Process mentions for notifications
|
||||
mentions := msg.GetMentions()
|
||||
for _, username := range mentions {
|
||||
sendMentionNotification(username, msg)
|
||||
}
|
||||
}
|
||||
|
||||
# Error Handling
|
||||
|
||||
All functions return appropriate errors for common failure cases:
|
||||
- Message not found (Find returns error for non-existent IDs)
|
||||
- Database connection issues
|
||||
- Invalid operations (e.g., saving/deleting messages without IDs)
|
||||
- Search query errors
|
||||
- Time range validation errors
|
||||
*/
|
||||
package babble
|
Loading…
x
Reference in New Issue
Block a user