# Bot Integration Guide

This guide explains how to integrate your bot with the Jackbox Game Picker API for live voting and game notifications.

## Table of Contents

1. [Live Voting (Bot → API)](#live-voting-bot--api)
2. [Game Notifications (API → Bot)](#game-notifications-api--bot)
3. [Webhook Management](#webhook-management)
4. [Testing](#testing)

---

## Live Voting (Bot → API)

Your bot can send real-time votes to the API when it detects "thisgame++" or "thisgame--" in Kosmi chat.

### Endpoint

```
POST /api/votes/live
```

### Authentication

Requires JWT token in Authorization header:

```
Authorization: Bearer YOUR_JWT_TOKEN
```

### Request Body

```json
{
  "username": "string",     // Username of the voter
  "vote": "up" | "down",    // "up" for thisgame++, "down" for thisgame--
  "timestamp": "string"     // ISO 8601 timestamp (e.g., "2025-11-01T20:30:00Z")
}
```

### Response (Success)

```json
{
  "success": true,
  "message": "Vote recorded successfully",
  "session": {
    "id": 123,
    "games_played": 5
  },
  "game": {
    "id": 45,
    "title": "Fibbage 4",
    "upvotes": 46,
    "downvotes": 3,
    "popularity_score": 43
  },
  "vote": {
    "username": "TestUser",
    "type": "up",
    "timestamp": "2025-11-01T20:30:00Z"
  }
}
```

### Error Responses

- **400 Bad Request**: Invalid payload or timestamp format
- **404 Not Found**: No active session or timestamp doesn't match any game
- **409 Conflict**: Duplicate vote (within 1 second of previous vote from same user)
- **500 Internal Server Error**: Server error

### Example Implementation (Node.js)

```javascript
// When bot detects "thisgame++" or "thisgame--" in Kosmi chat
async function handleVote(username, message) {
  const isUpvote = message.includes('thisgame++');
  const isDownvote = message.includes('thisgame--');
  
  if (!isUpvote && !isDownvote) return;
  
  try {
    const response = await fetch('http://your-api-url/api/votes/live', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${process.env.JWT_TOKEN}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        username: username,
        vote: isUpvote ? 'up' : 'down',
        timestamp: new Date().toISOString()
      })
    });
    
    const data = await response.json();
    
    if (response.ok) {
      console.log(`Vote recorded for ${data.game.title}: ${data.game.upvotes}👍 ${data.game.downvotes}👎`);
    } else {
      console.error('Vote failed:', data.error);
    }
  } catch (error) {
    console.error('Error sending vote:', error);
  }
}
```

### Important Notes

- **Deduplication**: Votes from the same user within 1 second are automatically rejected to prevent spam
- **Timestamp Matching**: The API matches the vote timestamp to the correct game based on when games were played
- **Active Session Required**: Votes can only be recorded when there's an active session with games played

---

## Game Notifications (API → Bot)

The API can send webhooks to your bot when games are added to a session, allowing you to announce "Coming up next: Game Title!" in Kosmi chat.

### Webhook Event: `game.added`

Triggered whenever a game is added to an active session (either via picker or manual selection).

### Webhook Payload

```json
{
  "event": "game.added",
  "timestamp": "2025-11-01T20:30:00Z",
  "data": {
    "session": {
      "id": 123,
      "is_active": true,
      "games_played": 5
    },
    "game": {
      "id": 45,
      "title": "Fibbage 4",
      "pack_name": "The Jackbox Party Pack 9",
      "min_players": 2,
      "max_players": 8,
      "manually_added": false
    }
  }
}
```

### Webhook Headers

The API sends the following headers with each webhook:

- `Content-Type: application/json`
- `X-Webhook-Signature: sha256=<hmac_signature>` - HMAC-SHA256 signature for verification
- `X-Webhook-Event: game.added` - Event type
- `User-Agent: Jackbox-Game-Picker-Webhook/1.0`

### Signature Verification

**IMPORTANT**: Always verify the webhook signature to ensure the request is authentic.

```javascript
const crypto = require('crypto');

function verifyWebhookSignature(signature, payload, secret) {
  if (!signature || !signature.startsWith('sha256=')) {
    return false;
  }
  
  const expectedSignature = 'sha256=' + crypto
    .createHmac('sha256', secret)
    .update(JSON.stringify(payload))
    .digest('hex');
  
  // Use timing-safe comparison
  try {
    return crypto.timingSafeEqual(
      Buffer.from(signature),
      Buffer.from(expectedSignature)
    );
  } catch (err) {
    return false;
  }
}
```

### Example Implementation (Express.js)

```javascript
const express = require('express');
const crypto = require('crypto');

const app = express();

// IMPORTANT: Use express.json() with verify option to get raw body
app.use(express.json({
  verify: (req, res, buf) => {
    req.rawBody = buf.toString('utf8');
  }
}));

app.post('/webhook/jackbox', (req, res) => {
  const signature = req.headers['x-webhook-signature'];
  const secret = process.env.WEBHOOK_SECRET; // Your webhook secret
  
  // Verify signature
  if (!signature || !signature.startsWith('sha256=')) {
    return res.status(401).send('Missing or invalid signature');
  }
  
  const expectedSignature = 'sha256=' + crypto
    .createHmac('sha256', secret)
    .update(req.rawBody)
    .digest('hex');
  
  // Timing-safe comparison
  try {
    if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expectedSignature))) {
      return res.status(401).send('Invalid signature');
    }
  } catch (err) {
    return res.status(401).send('Invalid signature');
  }
  
  // Handle the event
  if (req.body.event === 'game.added') {
    const game = req.body.data.game;
    
    // Send message to Kosmi chat
    sendKosmiMessage(`🎮 Coming up next: ${game.title}!`);
    
    console.log(`Announced game: ${game.title} from ${game.pack_name}`);
  }
  
  // Always respond with 200 OK
  res.status(200).send('OK');
});

function sendKosmiMessage(message) {
  // Your Kosmi chat integration here
  console.log('Sending to Kosmi:', message);
}

app.listen(3001, () => {
  console.log('Webhook receiver listening on port 3001');
});
```

---

## Webhook Management

You can manage webhooks through the API using the following endpoints (all require JWT authentication).

### List All Webhooks

```bash
GET /api/webhooks
Authorization: Bearer YOUR_JWT_TOKEN
```

### Create Webhook

```bash
POST /api/webhooks
Authorization: Bearer YOUR_JWT_TOKEN
Content-Type: application/json

{
  "name": "Kosmi Bot",
  "url": "http://your-bot-url/webhook/jackbox",
  "secret": "your_shared_secret_key",
  "events": ["game.added"]
}
```

### Update Webhook

```bash
PATCH /api/webhooks/:id
Authorization: Bearer YOUR_JWT_TOKEN
Content-Type: application/json

{
  "enabled": false  // Disable webhook
}
```

### Delete Webhook

```bash
DELETE /api/webhooks/:id
Authorization: Bearer YOUR_JWT_TOKEN
```

### Test Webhook

```bash
POST /api/webhooks/test/:id
Authorization: Bearer YOUR_JWT_TOKEN
```

Sends a test `game.added` event to verify your webhook is working.

### View Webhook Logs

```bash
GET /api/webhooks/:id/logs?limit=50
Authorization: Bearer YOUR_JWT_TOKEN
```

Returns recent webhook delivery attempts with status codes and errors.

---

## Testing

### Test Live Voting

```bash
# Get your JWT token first
curl -X POST "http://localhost:5000/api/auth/login" \
  -H "Content-Type: application/json" \
  -d '{"apiKey": "YOUR_API_KEY"}'

# Send a test vote
curl -X POST "http://localhost:5000/api/votes/live" \
  -H "Authorization: Bearer YOUR_JWT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "username": "TestUser",
    "vote": "up",
    "timestamp": "2025-11-01T20:30:00Z"
  }'
```

### Test Webhooks

```bash
# Create a webhook
curl -X POST "http://localhost:5000/api/webhooks" \
  -H "Authorization: Bearer YOUR_JWT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Test Webhook",
    "url": "http://localhost:3001/webhook/jackbox",
    "secret": "test_secret_123",
    "events": ["game.added"]
  }'

# Test the webhook
curl -X POST "http://localhost:5000/api/webhooks/test/1" \
  -H "Authorization: Bearer YOUR_JWT_TOKEN"

# Check webhook logs
curl -X GET "http://localhost:5000/api/webhooks/1/logs" \
  -H "Authorization: Bearer YOUR_JWT_TOKEN"
```

---

## Available Events

Currently supported webhook events:

- `game.added` - Triggered when a game is added to an active session

More events may be added in the future (e.g., `session.started`, `session.ended`, `vote.recorded`).

---

## Security Best Practices

1. **Always verify webhook signatures** - Never trust webhook payloads without verification
2. **Use HTTPS in production** - Webhook URLs should use HTTPS to prevent man-in-the-middle attacks
3. **Keep secrets secure** - Store webhook secrets in environment variables, never in code
4. **Implement rate limiting** - Protect your webhook endpoints from abuse
5. **Log webhook activity** - Keep logs of webhook deliveries for debugging
6. **Use strong secrets** - Generate cryptographically secure random strings for webhook secrets

---

## Troubleshooting

### Votes Not Being Recorded

- Check that there's an active session with games played
- Verify the timestamp is within the timeframe of a played game
- Ensure you're not sending duplicate votes within 1 second
- Check API logs for error messages

### Webhooks Not Being Received

- Verify your webhook URL is publicly accessible
- Check webhook logs via `/api/webhooks/:id/logs`
- Test with `ngrok` or similar tool if developing locally
- Ensure your webhook endpoint responds with 200 OK
- Check that webhook is enabled in the database

### Signature Verification Failing

- Ensure you're using the raw request body for signature verification
- Check that the secret matches what's stored in the database
- Verify you're using HMAC-SHA256 algorithm
- Make sure to prefix with "sha256=" when comparing

---

## Support

For issues or questions, contact: cottongin@cottongin.xyz

