Update REST endpoint docs (votes.md, sessions.md), WebSocket protocol (websocket.md), OpenAPI spec, and voting guide with the new GET /api/votes, GET /api/sessions/:id/votes, and vote.received event. Made-with: Cursor
7.4 KiB
Voting and Popularity
A narrative guide to how the Jackbox Game Picker handles community voting and game popularity. This system lets viewers and stream chat influence which games rise to the top—without directly controlling the random picker.
1. How Popularity Works
Every game has a popularity score stored in the database:
popularity_score = upvotes - downvotes
The score is computed from upvotes and downvotes and persisted per game. As votes accumulate across sessions, the score reflects community sentiment over time.
Important: Popularity is used for rankings (e.g., "top rated games" in stats) but does not directly affect picker weights. The random picker uses favor bias, not popularity, when selecting games.
2. Favor Bias vs Popularity
Two separate systems govern how games are treated:
| Aspect | Favor Bias | Popularity |
|---|---|---|
| Who controls it | Admin (via API) | Community (via votes) |
| Values | -1 (disfavor), 0 (neutral), 1 (favor) |
upvotes - downvotes (unbounded) |
| Affects picker? | Yes — directly changes weights | No |
| Purpose | Manual curation; push/penalize specific games | Community sentiment; rankings |
Favor bias affects picker probability directly. Setting favor_bias to 1 on a game boosts its weight; -1 reduces it. See Games favor endpoint and Picker weighted selection.
Popularity is driven entirely by viewer votes. It surfaces in stats (e.g., topRatedGames) and session game lists, but the picker does not read it. These systems are independent.
3. Two Voting Mechanisms
The API supports two ways to record votes: batch chat import (after the fact) and live votes (real-time from bots).
Chat Import (Batch, After-the-Fact)
Collect Twitch or YouTube chat logs containing thisgame++ (upvote) and thisgame-- (downvote), then submit them in bulk.
Flow:
- Export chat logs with
username,message, andtimestampfor each message. - Filter or pass messages; the API parses
thisgame++andthisgame--from themessagefield. - POST to
POST /api/sessions/{id}/chat-importwith achatDataarray of{ username, message, timestamp }. - The API matches each vote’s timestamp to the game that was playing at that time (using
played_atintervals). - Votes are deduplicated by SHA-256 hash of
username:message:timestamp. - Response includes
votesByGamebreakdown anddebuginfo (e.g., session timeline, vote matches).
See Sessions chat-import endpoint.
Live Votes (Real-Time, from Bots)
A bot sends individual votes during the stream. Each vote is processed immediately.
Flow:
- Bot detects
thisgame++orthisgame--(or equivalent) in chat. - Bot sends
POST /api/votes/livewith{ username, vote, timestamp }. votemust be"up"or"down".timestampmust be ISO 8601 (e.g.,2026-03-15T20:30:00Z).- The API finds the active session and matches the vote timestamp to the game playing at that time.
- Deduplication: Votes from the same username within 1 second are rejected with
409 Conflict.
See Votes live endpoint.
Real-time tracking: Live votes also broadcast a vote.received WebSocket event to all clients subscribed to the active session. This enables stream overlays and bots to react to votes in real-time without polling. See WebSocket vote.received.
3b. Querying Vote Data
Two endpoints expose vote data for reading:
GET /api/sessions/{id}/votes— Per-game vote breakdown for a session. Returns aggregatedupvotes,downvotes,net_score, andtotal_votesper game. See Sessions votes endpoint.GET /api/votes— Paginated global vote history with filtering bysession_id,game_id,username, andvote_type. Returns individual vote records. See Votes list endpoint.
4. Timestamp Matching Explained
Games in a session have a played_at timestamp. A vote’s timestamp determines which game it belongs to.
Rule: A vote belongs to the game whose played_at is the most recent one before the vote timestamp.
Example session timeline:
-
Game A:
played_at20:00 -
Game B:
played_at20:15 -
Game C:
played_at20:30 -
Vote at 20:10 → Game A (last
played_atbefore 20:10) -
Vote at 20:20 → Game B
-
Vote at 20:45 → Game C (last game in session; captures all votes after it started)
The last game in the session captures all votes that occur after its played_at.
5. How Stats Reflect Popularity
GET /api/stats returns aggregate statistics, including:
- mostPlayedGames — top 10 by
play_count(games withplay_count> 0). - topRatedGames — top 10 by
popularity_score(games withpopularity_score> 0).
Both are limited to the top 10 and exclude games with score/count ≤ 0. See Stats endpoint.
6. Example Requests
Chat Import
Import a batch of chat messages for session 5:
curl -X POST "http://localhost:5000/api/sessions/5/chat-import" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"chatData": [
{
"username": "viewer1",
"message": "thisgame++",
"timestamp": "2026-03-15T20:30:00Z"
},
{
"username": "viewer2",
"message": "thisgame--",
"timestamp": "2026-03-15T20:31:00Z"
},
{
"username": "viewer3",
"message": "thisgame++",
"timestamp": "2026-03-15T20:32:00Z"
}
]
}'
Sample response (200 OK):
{
"message": "Chat log imported and processed successfully",
"messagesImported": 3,
"duplicatesSkipped": 0,
"votesProcessed": 3,
"votesByGame": {
"42": {
"title": "Quiplash 3",
"upvotes": 2,
"downvotes": 1
}
},
"debug": {
"sessionGamesTimeline": [
{
"title": "Quiplash 3",
"played_at": "2026-03-15T20:00:00.000Z",
"played_at_ms": 1742068800000
}
],
"voteMatches": []
}
}
Live Vote
Submit a single live vote (requires active session):
curl -X POST "http://localhost:5000/api/votes/live" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"username": "viewer123",
"vote": "up",
"timestamp": "2026-03-15T20:30:00Z"
}'
Sample response (200 OK):
{
"success": true,
"message": "Vote recorded successfully",
"session": { "id": 3, "games_played": 5 },
"game": {
"id": 42,
"title": "Quiplash 3",
"upvotes": 11,
"downvotes": 2,
"popularity_score": 9
},
"vote": {
"username": "viewer123",
"type": "up",
"timestamp": "2026-03-15T20:30:00Z"
}
}
Related Documentation
- Sessions endpoints — chat import, session games,
played_at - Votes endpoints — live votes, deduplication, errors
- Stats endpoints —
mostPlayedGames,topRatedGames - Picker endpoints — weighted selection, favor bias (no popularity)
- Games endpoints — favor bias per game and pack