> [!IMPORTANT]  
> This project was developed entirely with AI coding assistance (Claude Opus 4.6 via Cursor IDE) and has not undergone rigorous review. It is provided as-is and may require adjustments for other environments.

# Jackbox Party Pack Game Picker

A full-stack web application that helps groups pick games to play from various Jackbox Party Packs. Features include random game selection with weighted filters, session tracking, game management, popularity scoring through chat log imports and live voting, and Jackbox lobby integration.

## Features

### Progressive Web App (PWA)
- **Installable**: Add to home screen on mobile and desktop devices
- **Offline Support**: Service worker provides offline functionality
- **Native Experience**: Runs like a native app when installed
- **Auto-updates**: Seamlessly updates to new versions

### Admin Features
- **Game Picker**: Randomly select games with intelligent filters
  - Filter by player count, drawing games, game length, and family-friendly status
  - Weighted random selection using game and pack favor bias
  - Automatic repeat avoidance (prevents same game or alternating pattern)
  - Manual game selection option
  - Real-time session tracking

- **Game Manager**: Complete CRUD operations for games and packs
  - Enable/disable individual games or entire packs
  - Set favor bias on games and packs to influence pick weighting
  - Import/export games via CSV
  - View statistics (play counts, upvotes, downvotes, popularity scores)
  - Add, edit, and delete games

- **Session Management**: Track gaming sessions over time
  - Create and close sessions
  - View session history with pagination and filters
  - Archive/unarchive sessions
  - Add, edit, and delete session notes (hidden from unauthenticated users)
  - Export session data
  - Bulk session creation
  - Per-session game management (add, remove, reorder, set status)

- **Room Code & Player Count**: Live Jackbox lobby integration
  - Set room codes on session games
  - Automatic player count fetching via Jackbox ecast shard API
  - Start/stop player count monitoring
  - Live status updates via WebSocket

- **Chat Log Import**: Process chat messages to assess game popularity
  - Supports "thisgame++" and "thisgame--" voting
  - Automatically matches votes to games based on timestamps
  - Updates upvote/downvote counts across sessions

- **Live Voting API**: Real-time vote processing from external bots
  - Accept live votes via REST API
  - Real-time `vote.received` WebSocket event for stream overlays
  - Per-session vote breakdown and paginated global vote history
  - Automatic deduplication (1-second window)
  - Timestamp-based game matching
  - JWT authentication for security

- **Webhook System**: Notify external services of events
  - Send notifications when games are added to sessions
  - HMAC-SHA256 signature verification
  - Webhook management (CRUD operations)
  - Delivery logging and testing

### Public Features
- View active session and games currently being played
- Browse session history (with archived sessions hidden by default)
- Detailed session view with game list and vote breakdowns
- See game statistics and popularity

## Tech Stack

- **Frontend**: React 18 with Vite, Tailwind CSS, React Router 6
- **Backend**: Node.js with Express 4
- **Database**: SQLite with better-sqlite3
- **Real-time**: WebSocket server (`ws`) for presence, subscriptions, and live events
- **Authentication**: JWT-based admin authentication
- **Deployment**: Docker with docker-compose (Node 22 Alpine + nginx Alpine)

## Prerequisites

- Docker and Docker Compose (recommended)
- OR Node.js 18+ and npm (for local development)

## Quick Start with Docker

1. **Clone the repository**
   ```bash
   git clone <repository-url>
   cd jackboxpartypack-gamepicker
   ```

2. **Create environment file**
   
   Create a `.env` file in the root directory:
   ```env
   JWT_SECRET=your-secret-jwt-key-change-this
   ADMIN_KEY=your-admin-key-here
   ```
   
   `JWT_SECRET` is required. Provide either `ADMIN_KEY` (single admin) or configure named admins (see [Named Admins](#named-admins) below).

3. **Build and start the containers**
   ```bash
   docker-compose up -d
   ```

4. **Access the application**
   - Frontend: http://localhost:3000
   - Backend API: http://localhost:5000

5. **Login as admin**
   - Navigate to the login page
   - Enter your `ADMIN_KEY` (or a named admin key) from your configuration

The database will be automatically initialized and populated with games from `games-list.csv` on first run.

## Local Development Setup

### Backend Setup

1. **Navigate to backend directory**
   ```bash
   cd backend
   ```

2. **Install dependencies**
   ```bash
   npm install
   ```

3. **Create environment file**
   
   Create `backend/.env`:
   ```env
   PORT=5000
   DB_PATH=./data/jackbox.db
   JWT_SECRET=your-secret-jwt-key
   ADMIN_KEY=admin123
   ```

4. **Start the backend server**
   ```bash
   npm run dev
   ```

The backend will run on http://localhost:5000

### Frontend Setup

1. **Navigate to frontend directory**
   ```bash
   cd frontend
   ```

2. **Install dependencies**
   ```bash
   npm install
   ```

3. **Start the development server**
   ```bash
   npm run dev
   ```

The frontend will run on http://localhost:3000. Note: the Vite dev proxy is configured to forward `/api` requests to `http://backend:5000` (the Docker Compose service name). For bare-metal local development without Docker, you may need to update the proxy target in `vite.config.js` to `http://localhost:5000`.

## Configuration

### Named Admins

The app supports multiple named admin accounts. Create `backend/config/admins.json` (see `admins.example.json` for the format):

```json
[
  { "name": "Alice", "key": "change-me-alice-key" },
  { "name": "Bob", "key": "change-me-bob-key" }
]
```

Each admin logs in with their unique key. Their name is embedded in the JWT and displayed in the presence bar.

If `admins.json` is not found, the app falls back to the `ADMIN_KEY` environment variable as a single admin named "Admin".

To use a custom path for the admins file, set `ADMIN_CONFIG_PATH` in your environment.

For Docker, uncomment the volume mount in `docker-compose.yml`:
```yaml
- ./backend/config/admins.json:/app/config/admins.json:ro
```

### Branding and Metadata

All app branding, metadata, and PWA configuration is centralized in `frontend/src/config/branding.js`. Edit this file to customize:

- **App Name** and **Short Name** - Displayed in UI and when installed as PWA
- **Description** - Shown in search engines and app stores
- **Version** - Current app version
- **Theme Color** - Primary color for browser chrome and PWA theme
- **Keywords** - SEO metadata
- **Author** - Creator/maintainer information
- **Links** - GitHub repo, support contact, etc.

When you update `branding.js`, the following are automatically synchronized:

1. **PWA Manifest** (`manifest.json`) - Generated at build time via `generate-manifest.js`
2. **HTML Meta Tags** - Updated via Vite HTML transformation plugin
3. **App UI** - Components import branding directly

To regenerate the manifest manually:
```bash
cd frontend
npm run generate-manifest
```

### Environment Variables

| Variable | Required | Default | Description |
|----------|----------|---------|-------------|
| `JWT_SECRET` | Yes | — | Secret key for signing JWT tokens |
| `ADMIN_KEY` | No | — | Single admin authentication key (fallback when `admins.json` is absent) |
| `ADMIN_CONFIG_PATH` | No | `backend/config/admins.json` | Path to named admins JSON file |
| `PORT` | No | `5000` | Backend server port |
| `NODE_ENV` | No | `development` | Environment (production/development) |
| `DB_PATH` | No | `./data/jackbox.db` | Path to SQLite database file |
| `DEBUG` | No | `false` | Enable debug logging |

## Testing

The project uses Jest for API and integration tests. Tests live in the `tests/` directory at the repository root.

```bash
# Install backend dependencies first
cd backend && npm install && cd ..

# Run tests
npx jest

# Run tests in watch mode
npx jest --watch
```

## Project Structure

```
/
├── backend/
│   ├── routes/              # API route handlers
│   │   ├── auth.js          # Authentication endpoints
│   │   ├── games.js         # Game CRUD, packs, favor bias
│   │   ├── sessions.js      # Sessions, archives, notes, room codes, export
│   │   ├── picker.js        # Game picker algorithm
│   │   ├── stats.js         # Statistics endpoints
│   │   ├── votes.js         # Live voting endpoint
│   │   └── webhooks.js      # Webhook management
│   ├── middleware/           # Express middleware
│   │   ├── auth.js          # JWT authentication (required)
│   │   └── optional-auth.js # JWT authentication (optional, for public routes)
│   ├── config/              # Configuration files
│   │   ├── admins.example.json  # Example named admins config
│   │   ├── admins.json          # Named admins (gitignored)
│   │   └── load-admins.js       # Admin config loader
│   ├── utils/               # Utility modules
│   │   ├── websocket-manager.js  # WebSocket server (presence, events)
│   │   ├── ecast-shard-client.js # Jackbox ecast shard integration
│   │   ├── jackbox-api.js        # Jackbox API helpers
│   │   ├── notes-preview.js      # Session notes preview generation
│   │   └── webhooks.js           # Webhook trigger and HMAC signatures
│   ├── database.js          # SQLite database setup and migrations
│   ├── bootstrap.js         # Database initialization from CSV
│   ├── server.js            # Express + WebSocket entry point
│   ├── package.json
│   └── Dockerfile
├── frontend/
│   ├── src/
│   │   ├── pages/           # React page components
│   │   │   ├── Home.jsx
│   │   │   ├── Login.jsx
│   │   │   ├── Picker.jsx
│   │   │   ├── Manager.jsx
│   │   │   ├── History.jsx
│   │   │   └── SessionDetail.jsx
│   │   ├── components/      # Reusable UI components
│   │   │   ├── PresenceBar.jsx
│   │   │   ├── Toast.jsx
│   │   │   ├── ThemeToggle.jsx
│   │   │   ├── Logo.jsx
│   │   │   ├── RoomCodeModal.jsx
│   │   │   ├── GamePoolModal.jsx
│   │   │   ├── PopularityBadge.jsx
│   │   │   ├── InstallPrompt.jsx
│   │   │   └── SafariInstallPrompt.jsx
│   │   ├── context/         # React context providers
│   │   │   ├── AuthContext.jsx
│   │   │   └── ThemeContext.jsx
│   │   ├── hooks/           # Custom React hooks
│   │   │   └── usePresence.js
│   │   ├── config/          # Frontend configuration
│   │   │   └── branding.js
│   │   ├── api/             # API client
│   │   │   └── axios.js
│   │   ├── App.jsx
│   │   ├── main.jsx
│   │   └── index.css
│   ├── index.html
│   ├── vite.config.js
│   ├── tailwind.config.js
│   ├── package.json
│   ├── nginx.conf           # Nginx config for Docker (proxy + SPA)
│   └── Dockerfile
├── tests/                   # Jest API and integration tests
│   ├── api/                 # Route-level tests
│   ├── helpers/             # Test utilities
│   └── jest.setup.js
├── scripts/                 # Jackbox lobby inspection utilities
├── docs/                    # API documentation, design specs, plans
│   ├── api/                 # OpenAPI spec, endpoint docs, guides
│   ├── plans/               # Implementation plans
│   └── archive/             # Archived docs
├── docker-compose.yml
├── jest.config.js
├── games-list.csv           # Initial game seed data
└── README.md
```

## API Endpoints

### Health
- `GET /health` - Health check (returns `{ status: "ok" }`)

### Authentication
- `POST /api/auth/login` - Login with admin key (returns JWT with admin name)
- `POST /api/auth/verify` - Verify JWT token

### Games
- `GET /api/games` - List all games (with filters)
- `GET /api/games/packs` - List unique pack names
- `GET /api/games/meta/packs` - Get pack list with stats and favor bias
- `GET /api/games/:id` - Get single game
- `POST /api/games` - Create game (admin)
- `PUT /api/games/:id` - Update game (admin)
- `DELETE /api/games/:id` - Delete game (admin)
- `PATCH /api/games/:id/toggle` - Toggle game enabled status (admin)
- `PATCH /api/games/:id/favor` - Set game favor bias (admin)
- `PATCH /api/games/packs/:name/toggle` - Toggle entire pack (admin)
- `PATCH /api/games/packs/:name/favor` - Set pack favor bias (admin)
- `GET /api/games/export/csv` - Export games to CSV (admin)
- `POST /api/games/import/csv` - Import games from CSV (admin)

### Sessions
- `GET /api/sessions` - List sessions (paginated, filterable, `X-Total-Count` header)
- `GET /api/sessions/active` - Get active session
- `GET /api/sessions/:id` - Get single session (notes hidden from unauthenticated users)
- `POST /api/sessions` - Create new session (admin)
- `POST /api/sessions/bulk` - Bulk create sessions (admin)
- `POST /api/sessions/:id/close` - Close session (admin)
- `DELETE /api/sessions/:id` - Delete session (admin)
- `PUT /api/sessions/:id/notes` - Update session notes (admin)
- `DELETE /api/sessions/:id/notes` - Delete session notes (admin)
- `POST /api/sessions/:id/archive` - Archive session (admin)
- `POST /api/sessions/:id/unarchive` - Unarchive session (admin)
- `GET /api/sessions/:id/games` - Get games in session
- `POST /api/sessions/:id/games` - Add game to session (admin)
- `PATCH /api/sessions/:sessionId/games/:gameId/status` - Update game status (admin)
- `DELETE /api/sessions/:sessionId/games/:gameId` - Remove game from session (admin)
- `PATCH /api/sessions/:sessionId/games/:gameId/room-code` - Set room code (admin)
- `PATCH /api/sessions/:sessionId/games/:gameId/player-count` - Update player count (admin)
- `GET /api/sessions/:sessionId/games/:gameId/status-live` - Get live ecast status
- `POST /api/sessions/:sessionId/games/:gameId/start-player-check` - Start player count monitoring (admin)
- `POST /api/sessions/:sessionId/games/:gameId/stop-player-check` - Stop player count monitoring (admin)
- `GET /api/sessions/:id/votes` - Get per-game vote breakdown for a session
- `POST /api/sessions/:id/chat-import` - Import chat log (admin)
- `GET /api/sessions/:id/export` - Export session data (admin)

### Game Picker
- `POST /api/pick` - Pick random game with filters and favor bias weighting

### Statistics
- `GET /api/stats` - Get overall statistics

### Votes
- `GET /api/votes` - Paginated vote history with filtering
- `POST /api/votes/live` - Submit real-time vote (admin)

### Webhooks
- `GET /api/webhooks` - List all webhooks (admin)
- `GET /api/webhooks/:id` - Get single webhook (admin)
- `POST /api/webhooks` - Create webhook (admin)
- `PATCH /api/webhooks/:id` - Update webhook (admin)
- `DELETE /api/webhooks/:id` - Delete webhook (admin)
- `POST /api/webhooks/test/:id` - Test webhook (admin)
- `GET /api/webhooks/:id/logs` - Get webhook logs (admin)

### WebSocket
- `ws://host/api/sessions/live` - Real-time connection for presence, session subscriptions, and live events

## Usage Guide

### Starting a Game Session

1. Login as admin
2. Navigate to the Picker page
3. A new session will be created automatically if none exists
4. Set filters (player count, drawing preference, length, family-friendly)
5. Click "Roll the Dice" to get a random game
6. Review the suggested game and either:
   - Click "Play This Game" to accept
   - Click "Re-roll" to get a different game
7. The game is added to the session and play count is incremented

### Managing Games

1. Login as admin
2. Navigate to the Manager page
3. View statistics and pack information
4. Toggle individual games or entire packs on/off
5. Adjust favor bias to weight certain games or packs in the picker
6. Add new games with the "+ Add Game" button
7. Edit or delete existing games
8. Import/Export games via CSV

### Closing a Session and Importing Chat Logs

1. Navigate to the History page
2. Select the active session
3. Click "Import Chat Log"
4. Paste JSON array of chat messages with format:
   ```json
   [
     {
       "username": "Alice",
       "message": "thisgame++",
       "timestamp": "2024-10-30T20:15:00Z"
     },
     {
       "username": "Bob",
       "message": "This is fun! thisgame++",
       "timestamp": "2024-10-30T20:16:30Z"
     }
   ]
   ```
5. The system will match votes to games based on timestamps
6. Click "Close Session" to finalize
7. Add optional notes about the session

### Archiving Sessions

Closed sessions can be archived to keep the history page clean. Archived sessions are hidden by default but can be viewed by toggling the archive filter. Archiving and unarchiving are available from the session detail page or via bulk actions in the history list.

## Chat Log Format

The chat import feature expects a JSON array where each message has:
- `username`: String - Name of the chatter
- `message`: String - The chat message (may contain "thisgame++" or "thisgame--")
- `timestamp`: String - ISO 8601 timestamp

The system will:
1. Parse each message for vote patterns
2. Match the timestamp to the game being played at that time
3. Update the game's upvote/downvote counts (+1 for ++, -1 for --)
4. Store the chat log in the database

## Bot Integration

For integrating external bots (e.g., for live voting and game notifications), see **[BOT_INTEGRATION.md](docs/archive/BOT_INTEGRATION.md)** for detailed documentation including:

- Live voting API usage
- **WebSocket integration (recommended)** for real-time game notifications
- Webhook setup and verification (alternative to WebSocket)
- Example implementations in Node.js and Go
- Security best practices

## Jackbox Player Count Fetcher

The `scripts/` directory contains utilities for inspecting Jackbox game lobbies:

- **[get-player-count.go](scripts/get-player-count.go)** - Go + chromedp script (recommended, most reliable)
- **[get-player-count.html](scripts/get-player-count.html)** - Browser-based tool (no installation required!)
- **[get-jackbox-player-count.js](scripts/get-jackbox-player-count.js)** - Node.js script (limited, may not work)

See **[scripts/README.md](scripts/README.md)** for detailed usage instructions.

### Quick Start

**Go version (recommended for automation):**
```bash
cd scripts
go run get-player-count.go JYET
```

**Browser version (easiest for manual testing):**
1. Open `scripts/get-player-count.html` in any browser
2. Enter a 4-letter room code
3. View real-time player count and lobby status

**How it works:**
- Automates joining jackbox.tv through Chrome/Chromium
- Captures WebSocket messages containing player data
- Extracts actual player count from lobby state

These tools retrieve:
- Actual player count (not just max capacity)
- List of current players and their roles (host/player)
- Game state and lobby status  
- Audience count

**Note:** Direct WebSocket connection is not possible without authentication, so the tools join through jackbox.tv to capture the data.

## Database Schema

### games
- id, pack_name, title, min_players, max_players, length_minutes
- has_audience, family_friendly, game_type, secondary_type
- play_count, popularity_score, upvotes, downvotes, favor_bias, enabled, created_at

### packs
- id, name (unique), favor_bias, created_at

### sessions
- id, created_at, closed_at, is_active, notes, archived

### session_games
- id, session_id, game_id, played_at, manually_added, status
- room_code, player_count, player_count_check_status

### chat_logs
- id, session_id, chatter_name, message, timestamp, parsed_vote, message_hash

### live_votes
- id, session_id, game_id, username, vote_type, timestamp, created_at

### webhooks
- id, name, url, secret, events, enabled, created_at

### webhook_logs
- id, webhook_id, event_type, payload, response_status, error_message, created_at

## Game Selection Algorithm

The picker uses the following logic:

1. **Filter eligible games**:
   - Only enabled games
   - Match player count range (if specified)
   - Filter by drawing type (if specified)
   - Filter by length range (if specified)
   - Filter by family-friendly status (if specified)

2. **Apply repeat avoidance**:
   - Get last 2 games from current session
   - Exclude those games from selection pool
   - This prevents immediate repeats and alternating patterns

3. **Weighted random selection**:
   - Games and packs can have a `favor_bias` (+/- integer)
   - Positive bias increases pick probability; negative bias decreases it
   - Pick a random game from remaining eligible games, weighted by bias
   - Return game details and pool size

## Docker Deployment

### Using Docker Compose

The provided `docker-compose.yml` sets up both frontend and backend services:

```bash
# Build and start
docker-compose up -d

# View logs
docker-compose logs -f

# Stop services
docker-compose down

# Rebuild after changes
docker-compose up -d --build
```

### Data Persistence

The SQLite database is stored in a named Docker volume (`jackbox-data`) mapped to `/app/data` in the backend container. To backup your data, use `docker cp` or bind-mount a host directory.

## Troubleshooting

### Database not initializing
- Ensure `games-list.csv` is in the root directory
- Check backend logs: `docker-compose logs backend`
- Manually delete the database to force re-initialization

### Can't login as admin
- Verify your `ADMIN_KEY` or `admins.json` is configured correctly
- Check that the `.env` file is loaded correctly
- If using named admins, ensure `admins.json` has valid JSON with unique names and keys
- Try restarting the backend service

### Frontend can't reach backend
- Verify both containers are running: `docker-compose ps`
- Check network connectivity: `docker-compose logs frontend`
- Ensure nginx.conf proxy settings are correct
- For local dev, confirm the Vite proxy target matches your backend URL

### Games not showing up
- Check if games are enabled in the Manager
- Verify filters aren't excluding all games
- Check database has games: View in Manager page

## License

MIT

## Contributing

Feel free to submit issues and pull requests!
