package bot import ( "context" "database/sql" "encoding/json" "fmt" "strings" "time" ) // LeaderboardConfig: "channel_id" is where the daily recap lands. type LeaderboardConfig struct { ChannelID string `json:"channel_id"` } // LeaderboardBot posts a daily shitpost recap. func LeaderboardBot(ctx context.Context, db *sql.DB, raw json.RawMessage, send SendMessageFunc) { var cfg LeaderboardConfig if err := json.Unmarshal(raw, &cfg); err != nil || cfg.ChannelID == "" { return } ticker := time.NewTicker(24 * time.Hour) defer ticker.Stop() // Post immediately on start, then daily postLeaderboard(db, cfg.ChannelID, send) for { select { case <-ctx.Done(): return case <-ticker.C: postLeaderboard(db, cfg.ChannelID, send) } } } func postLeaderboard(db *sql.DB, channelID string, send SendMessageFunc) { // ponytail: global lock on count. per-user aggregates if throughput matters. since := time.Now().Add(-24 * time.Hour) rows, err := db.QueryContext(context.Background(), ` SELECT u.username, COUNT(*) AS msg_count FROM messages m JOIN users u ON m.author_id = u.id WHERE m.created_at > $1 AND m.bot_id IS NULL GROUP BY u.username ORDER BY msg_count DESC LIMIT 10 `, since) if err != nil { return } defer rows.Close() var b strings.Builder b.WriteString("🏆 **24H SHITPOST LEADERBOARD** 🏆\n") rank := 1 for rows.Next() { var username string var count int if err := rows.Scan(&username, &count); err != nil { continue } medal := "" switch rank { case 1: medal = "🥇" case 2: medal = "🥈" case 3: medal = "🥉" } fmt.Fprintf(&b, "%s #%d **%s** — %d msgs\n", medal, rank, username, count) rank++ } if rank == 1 { b.WriteString("*crickets*\n") } send(channelID, strings.TrimSpace(b.String())) }