Files
jackboxpartypack-gamepicker/docs/api/guides/voting-and-popularity.md
cottongin 3ed3af06ba docs: add vote tracking endpoints to API documentation
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
2026-03-15 19:16:23 -04:00

7.4 KiB
Raw Blame History

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:

  1. Export chat logs with username, message, and timestamp for each message.
  2. Filter or pass messages; the API parses thisgame++ and thisgame-- from the message field.
  3. POST to POST /api/sessions/{id}/chat-import with a chatData array of { username, message, timestamp }.
  4. The API matches each votes timestamp to the game that was playing at that time (using played_at intervals).
  5. Votes are deduplicated by SHA-256 hash of username:message:timestamp.
  6. Response includes votesByGame breakdown and debug info (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:

  1. Bot detects thisgame++ or thisgame-- (or equivalent) in chat.
  2. Bot sends POST /api/votes/live with { username, vote, timestamp }.
  3. vote must be "up" or "down".
  4. timestamp must be ISO 8601 (e.g., 2026-03-15T20:30:00Z).
  5. The API finds the active session and matches the vote timestamp to the game playing at that time.
  6. 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 aggregated upvotes, downvotes, net_score, and total_votes per game. See Sessions votes endpoint.
  • GET /api/votes — Paginated global vote history with filtering by session_id, game_id, username, and vote_type. Returns individual vote records. See Votes list endpoint.

4. Timestamp Matching Explained

Games in a session have a played_at timestamp. A votes 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_at 20:00

  • Game B: played_at 20:15

  • Game C: played_at 20:30

  • Vote at 20:10 → Game A (last played_at before 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 with play_count > 0).
  • topRatedGames — top 10 by popularity_score (games with popularity_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"
  }
}