Skip to content

Real-time Twitch chat sentiment overlay for OBS — track viewer mood as a live tug-of-war bar

License

Notifications You must be signed in to change notification settings

pscheid92/chatpulse

Repository files navigation

ChatPulse

Tests Go Report Card

A real-time chat sentiment tracking overlay for Twitch streamers. Monitor your chat's mood during polls, debates, or Q&A sessions with a glassmorphism overlay for OBS.

A dedicated bot account reads chat on behalf of all streamers, making this a multi-tenant SaaS that supports horizontal scaling via Redis.

Features

  • Real-time sentiment tracking from Twitch chat messages via EventSub webhooks
  • Two display modes: combined tug-of-war bar or split positive/negative bars
  • Customizable triggers and labels for "for" and "against" votes
  • Sliding-window memory (5-120s) controls how long votes count
  • Multi-instance scaling with Redis for horizontal deployment
  • Bot account architecture — streamers only grant channel:bot scope; a single bot reads all channels
  • Token encryption at rest with AES-256-GCM
  • Overlay URL rotation to invalidate old URLs
  • Per-user debouncing (1s) to prevent spam
  • Zero-cost idle — skips processing when no overlay viewers are connected
  • Prometheus metrics for HTTP, vote processing, cache, and WebSocket
  • Correlation IDs for end-to-end request tracing
  • Audit logging for sensitive operations (login, config changes, UUID rotation)
  • Per-IP rate limiting on all route groups
  • Security headers (HSTS, CSP, X-Frame-Options, etc.)

Prerequisites

  1. Twitch Application: Register at https://dev.twitch.tv/console/apps

    • Get your Client ID and Client Secret
    • Set the OAuth Redirect URL to http://localhost:8080/auth/callback (or your production domain)
  2. Twitch Bot Account: A dedicated Twitch account that will read chat

    • Authorize the bot with user:read:chat and user:bot scopes (one-time setup)
    • Note the bot's Twitch user ID for BOT_USER_ID
  3. PostgreSQL: Version 18 or higher (required for uuidv7())

  4. Redis: Version 7+

  5. Public HTTPS URL: Required for EventSub webhook delivery (use ngrok for local development)

  6. Go: Version 1.26+ (for local development only)

Quick Start with Docker

  1. Clone the repository:
git clone https://github.com/pscheid92/chatpulse.git
cd chatpulse
  1. Create a .env file from the example:
cp .env.example .env
  1. Edit .env with your credentials:
TWITCH_CLIENT_ID=your_client_id
TWITCH_CLIENT_SECRET=your_client_secret
TWITCH_REDIRECT_URI=http://localhost:8080/auth/callback
SESSION_SECRET=$(openssl rand -hex 32)
WEBHOOK_CALLBACK_URL=https://your-subdomain.ngrok-free.app/webhooks/eventsub
WEBHOOK_SECRET=$(openssl rand -hex 16)
BOT_USER_ID=your_bot_twitch_user_id
  1. Start the application:
make docker-up
  1. Open http://localhost:8080 in your browser.

Local Development

  1. Install dependencies:
make deps
  1. Start PostgreSQL and Redis (or use Docker):
docker run -d \
  --name chatpulse-postgres \
  -e POSTGRES_USER=twitchuser \
  -e POSTGRES_PASSWORD=twitchpass \
  -e POSTGRES_DB=twitchdb \
  -p 5432:5432 \
  postgres:18-alpine

docker run -d \
  --name chatpulse-redis \
  -p 6379:6379 \
  redis:8-alpine
  1. Expose your local server for webhooks:
ngrok http 8080
  1. Set up your .env file as described above (use the ngrok URL for WEBHOOK_CALLBACK_URL).

  2. Run the server:

make run

Make Targets

make build             # Build binary -> ./server
make run               # Build and run locally
make test              # Run all tests (unit + integration, ~15s)
make test-short        # Run unit tests only (fast, <2s, no Docker)
make test-unit         # Alias for test-short
make test-integration  # Run integration tests only (~12s, requires Docker)
make test-race         # Run tests with race detector
make test-coverage     # Generate coverage report
make fmt               # Format code
make lint              # Run golangci-lint
make deps              # Download and tidy dependencies
make sqlc              # Regenerate sqlc code
make docker-build      # Build Docker image
make docker-up         # Start with Docker Compose (app + PostgreSQL 18 + Redis 8)
make docker-down       # Stop Docker Compose
make clean             # Remove build artifacts

Testing

make test           # Run all tests (unit + integration, ~15s)
make test-short     # Run unit tests only (skip integration, <2s)
make test-race      # Run with race detector
make test-coverage  # Generate coverage report

TDD workflow:

  • Use make test-short for rapid feedback during development (<2s, no Docker)
  • Run make test before committing (full suite with testcontainers)
  • CI runs full suite on every push

Usage

1. Configure Your Overlay

  1. Visit http://localhost:8080 and log in with your Twitch account
  2. Configure your sentiment triggers:
    • For Trigger: Word/phrase viewers type to vote "for" (e.g., "yes", "agree")
    • Against Trigger: Word/phrase to vote "against" (e.g., "no", "disagree")
    • Labels: Display labels for each side
    • Memory: How long votes count in the sliding window (5-120s, or infinite)
    • Display Mode: Combined (tug-of-war) or Split (two bars)
  3. Click "Save Configuration"

2. Add to OBS

  1. Copy your unique overlay URL from the dashboard
  2. In OBS, add a new Browser Source
  3. Paste your overlay URL
  4. Set dimensions: 800x100 (adjust to preference)

3. During Your Stream

  • Use the Reset to Center button on the dashboard to reset the sentiment bar
  • Use Rotate Overlay URL to generate a new URL and invalidate the old one

Environment Variables

See .env.example for all variables with comments.

Required:

  • DATABASE_URL — PostgreSQL connection string
  • REDIS_URL — Redis connection string (e.g., redis://localhost:6379)
  • TWITCH_CLIENT_ID / TWITCH_CLIENT_SECRET — Twitch app credentials
  • TWITCH_REDIRECT_URI — OAuth callback URL
  • SESSION_SECRET — Secret for session cookies
  • WEBHOOK_CALLBACK_URL — Public HTTPS URL for EventSub webhook delivery
  • WEBHOOK_SECRET — HMAC secret for webhook verification (10-100 chars)
  • BOT_USER_ID — Twitch user ID of the bot account

Optional:

  • APP_ENVdevelopment (default) or production (controls secure cookies)
  • PORT — Server port (default: 8080)
  • LOG_LEVELdebug, info, warn, error (default: info)
  • LOG_FORMATtext (default) or json (recommended for production)
  • MAX_WEBSOCKET_CONNECTIONS — File descriptor limit check (default: 10000)
  • SESSION_MAX_AGE — Cookie expiry (default: 168h / 7 days)
  • SHUTDOWN_TIMEOUT — Graceful shutdown deadline (default: 10s)

How It Works

  • Bot Account: A single bot account reads chat in all connected channels via EventSub webhooks
  • Webhooks + Conduits: Chat messages arrive via Twitch EventSub webhooks transported through a Conduit, verified with HMAC-SHA256
  • Vote Processing: Messages matching trigger words exactly (case-insensitive) are counted as votes
  • Debouncing: Each viewer can vote once per second to prevent spam
  • Sliding-window Counting: Votes are recorded in Redis Streams; sentiment is computed over a configurable time window (old votes naturally expire)
  • Real-time Broadcast: Updates are pushed to overlay clients via Centrifuge WebSocket with Redis broker for cross-instance delivery
  • Client-side Lerp: The overlay uses requestAnimationFrame for smooth animation toward server ratios with zero server cost
  • Rate Limiting: Per-IP rate limits on auth, dashboard/API, and webhook routes
  • Correlation IDs: Every request gets a unique ID propagated through logs for tracing
  • Audit Logging: Sensitive operations (login, logout, config save, reset, URL rotation) emit structured audit logs

Architecture

  • Backend: Single Go binary (Echo v4) serving HTTP, WebSocket, and webhook endpoints
  • Database: PostgreSQL 18+ with auto-migrations (tern) for streamers, configs, and EventSub subscriptions
  • Caching: 3-layer read-through cache (in-memory 10s → Redis 1h → PostgreSQL) with pub/sub invalidation
  • Scaling: Multi-instance via Redis (Streams for vote counting, pub/sub for broadcasting, Centrifuge Redis broker for WebSocket fan-out)
  • Observability: Structured logging (slog) with correlation IDs, audit logs, Prometheus metrics (/metrics endpoint)
  • Security: Per-IP rate limiting, security headers (HSTS, CSP), WebSocket origin validation, production SSL enforcement
  • Frontend: Minimal HTML/CSS/JS with no external dependencies, embedded via go:embed

Production Deployment

  1. Use HTTPS with a reverse proxy (nginx/Caddy) for SSL termination
  2. Set TWITCH_REDIRECT_URI to your production domain
  3. Generate strong secrets:
    SESSION_SECRET=$(openssl rand -hex 32)
    WEBHOOK_SECRET=$(openssl rand -hex 16)
  4. Set APP_ENV=production for secure cookies (also enforces DATABASE_URL SSL — rejects sslmode=disable/allow)
  5. Set LOG_FORMAT=json for structured logging
  6. Configure PostgreSQL backups
  7. Prometheus metrics are available at /metrics for monitoring

Troubleshooting

Overlay not connecting?

  • Verify the server is running and the overlay URL is correct
  • Check browser console for WebSocket errors

Chat messages not being tracked?

  • Ensure webhook delivery is working (check server logs for EventSub notifications)
  • Verify the bot account has authorized user:read:chat + user:bot scopes
  • Confirm BOT_USER_ID matches the bot's Twitch user ID
  • Check that WEBHOOK_CALLBACK_URL is publicly reachable over HTTPS

Webhooks not arriving?

  • For local dev, ensure ngrok is running and the URL in .env matches
  • Check that WEBHOOK_SECRET is at least 10 characters

License

MIT License - see LICENSE file for details.

About

Real-time Twitch chat sentiment overlay for OBS — track viewer mood as a live tug-of-war bar

Topics

Resources

License

Contributing

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors 2

  •  
  •  

Languages