Files
IRC-kosmi-relay/BOT_features.md
cottongin 9143a0bc60 here goes
2025-10-31 23:56:16 -04:00

9.8 KiB

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)
  2. Game Notifications (API → Bot)
  3. Webhook Management
  4. 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

{
  "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)

{
  "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)

// 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

{
  "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.

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)

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

GET /api/webhooks
Authorization: Bearer YOUR_JWT_TOKEN

Create Webhook

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

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

{
  "enabled": false  // Disable webhook
}

Delete Webhook

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

Test Webhook

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

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

# 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

# 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