415 lines
9.8 KiB
Markdown
415 lines
9.8 KiB
Markdown
# 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
|
|
|