docs: comprehensive documentation overhaul

Rewrite README.md and all setup guides to reflect the current native
GraphQL WebSocket architecture (replacing stale headless Chrome/WebSocket
interception descriptions). Add new docs/IRC.md with complete IRC command
reference, vote syntax, and ticker symbol table.

Archive pre-edit docs to docs/archive/setup-backup-2026-05-10/ and move
historical development notes from docs/ root into docs/archive/.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
cottongin
2026-05-12 22:01:25 -04:00
parent 4188ae29af
commit c88b75f30d
57 changed files with 4530 additions and 1994 deletions

View File

@@ -1,414 +1,145 @@
# Bot Integration Guide
# Bot Features Guide
This guide explains how to integrate your bot with the Jackbox Game Picker API for live voting and game notifications.
This document describes the features available in the Kosmi-IRC relay bot when the Jackbox Game Picker integration is enabled.
## Table of Contents
## Feature Summary
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)
| Feature | Where | Description |
|---------|-------|-------------|
| Message relay | IRC + Kosmi | Bidirectional chat relay |
| Vote detection | IRC + Kosmi | Automatic `thisgame++/--` and ticker votes |
| `!votes` command | IRC + Kosmi | Query current game vote tally |
| `!kreconnect` | IRC | Reconnect Kosmi bridge |
| `!jreconnect` | IRC | Reconnect Jackbox WebSocket |
| `!reconnect` | IRC | Reconnect all non-IRC services |
| Game announcements | All bridges | Jackbox game/session notifications |
| Room code images | All bridges | Generated room code images in chat |
| Mute toggle | Operator (SIGUSR1) | Suppress Jackbox announcements |
---
## Vote Detection
## Live Voting (Bot → API)
The bot automatically detects votes in chat messages from both IRC and Kosmi. Votes are submitted to the Jackbox Game Picker API.
Your bot can send real-time votes to the API when it detects "thisgame++" or "thisgame--" in Kosmi chat.
### Current Game Votes
### Endpoint
- `thisgame++` -- Upvote the currently playing game
- `thisgame--` -- Downvote the currently playing game
```
POST /api/votes/live
```
Case-insensitive. Can appear anywhere in the message. The message is still relayed normally.
### Ticker Symbol Votes
- `SYMBOL++` -- Upvote a specific game by ticker symbol
- `SYMBOL--` -- Downvote a specific game by ticker symbol
Symbols are 2-4 alphanumeric characters. See [IRC.md](../IRC.md) for the complete ticker table.
### Loop Prevention
Messages prefixed with `[irc]` or `[kosmi]` (relayed messages) are excluded from vote detection to prevent duplicate counting.
### Deduplication
The API rejects duplicate votes from the same user within 1 second.
## Game Announcements
When connected to the Jackbox API via WebSocket (default) or webhook, the bot broadcasts events to all bridges:
| Event | Example Message |
|-------|----------------|
| Session started | `🎮 Game Night is starting!` |
| Game added | `🎮 Coming up next: Fibbage 4 - Room Code ABCD` |
| Session ended | `🌙 Game Night has ended! Thanks for playing!` |
### Room Code Images
When `EnableRoomCodeImage=true`, game announcements include an uploaded image of the room code. The image is uploaded to the Kosmi CDN. Timing is controlled by `RoomCodeImageDelay` and `RoomCodePlaintextDelay` in config.
If image upload fails, the bot falls back to plaintext.
## IRC Commands
All commands are available to any user in the channel. See [IRC.md](../IRC.md) for complete details.
| Command | Effect |
|---------|--------|
| `!kreconnect` | Reconnect Kosmi bridge |
| `!jreconnect` | Reconnect Jackbox WebSocket |
| `!reconnect` | Reconnect all non-IRC services |
| `!votes` | Show current game vote tally |
`!votes` is also available from Kosmi chat.
## Mute Control
Jackbox announcements can be suppressed without stopping the bot:
- **Start muted**: `./matterbridge -conf matterbridge.toml -muted`
- **Toggle at runtime**: `kill -SIGUSR1 <pid>` or `docker kill -s SIGUSR1 <container>`
When muted, vote detection and normal message relay continue to work. Only Jackbox-driven announcements (session start/end, game added) are suppressed.
See [MUTE_CONTROL.md](MUTE_CONTROL.md) for full details.
## API Communication
### Authentication
Requires JWT token in Authorization header:
The bot authenticates with `POST /api/auth/login` using the `AdminPassword` from config. The JWT is cached in memory and refreshed on 401 responses.
```
Authorization: Bearer YOUR_JWT_TOKEN
```
### Vote Submission
### Request Body
Votes are submitted via `POST /api/votes/live` with:
```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",
"username": "voter_name",
"vote": "up",
"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
}
}
"ticker": "FBG4"
}
```
### Webhook Headers
The `ticker` field is omitted for `thisgame++/--` votes.
The API sends the following headers with each webhook:
### Event Transport
- `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`
**WebSocket (default)**: Connects to `wss://<api>/api/sessions/live`, authenticates, and subscribes to the active session. Events arrive in real-time with auto-reconnect.
### Signature Verification
**Webhook (fallback)**: Starts an HTTP server listening for `POST /webhook/jackbox` with HMAC-SHA256 signature verification.
**IMPORTANT**: Always verify the webhook signature to ensure the request is authentic.
## Configuration
```javascript
const crypto = require('crypto');
See [JACKBOX_INTEGRATION.md](JACKBOX_INTEGRATION.md) for the full configuration reference.
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;
}
}
Minimal setup:
```toml
[jackbox]
Enabled=true
APIURL="https://your-jackbox-api.example.com"
AdminPassword="your_password"
UseWebSocket=true
```
### 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
### Votes not registering
- 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
- Check that Jackbox integration is enabled
- Verify an active session exists
- Look for `"Detected vote from"` in debug logs
- Look for `"Failed to send vote"` errors
### Webhooks Not Being Received
### Announcements not appearing
- 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
- Check that the Jackbox WebSocket is connected
- Verify the bot is not muted (check logs for `MUTED`)
- Use `!jreconnect` to force a WebSocket reconnection
### 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
### `!votes` returns nothing
- An active session must exist
- A game must currently have "playing" status
- The Jackbox integration must be enabled and authenticated