package bot import ( "context" "database/sql" "encoding/json" "fmt" "strings" "time" ) // ConfessConfig: "channel_id" is where anonymous confessions land. type ConfessConfig struct { ChannelID string `json:"channel_id"` } // ConfessBot watches for /confess messages, deletes the original, reposts anonymously. func ConfessBot(ctx context.Context, db *sql.DB, raw json.RawMessage, send SendMessageFunc) { var cfg ConfessConfig if err := json.Unmarshal(raw, &cfg); err != nil || cfg.ChannelID == "" { return } lastID := "" ticker := time.NewTicker(10 * time.Second) defer ticker.Stop() for { select { case <-ctx.Done(): return case <-ticker.C: confessPoll(db, cfg.ChannelID, &lastID, send) } } } func confessPoll(db *sql.DB, targetChannel string, lastID *string, send SendMessageFunc) { q := `SELECT id, channel_id, content FROM messages WHERE content ILIKE '/confess%' AND bot_id IS NULL ORDER BY created_at ASC LIMIT 10` if *lastID != "" { q = `SELECT id, channel_id, content FROM messages WHERE content ILIKE '/confess%' AND bot_id IS NULL AND created_at::text > (SELECT created_at::text FROM messages WHERE id = $1) ORDER BY created_at ASC LIMIT 10` } var rows *sql.Rows var err error if *lastID != "" { rows, err = db.QueryContext(context.Background(), q, *lastID) } else { rows, err = db.QueryContext(context.Background(), q) } if err != nil { return } defer rows.Close() for rows.Next() { var id, channelID, content string if err := rows.Scan(&id, &channelID, &content); err != nil { continue } *lastID = id // Strip "/confess" prefix text := strings.TrimSpace(strings.TrimPrefix(content, "/confess")) if text == "" { continue } // Delete the original message db.ExecContext(context.Background(), `DELETE FROM messages WHERE id = $1`, id) // Repost anonymously send(targetChannel, fmt.Sprintf("🕵️ **anonymous confession:** %s", text)) } }