A webapp that generates weekly trivia games from J! Archive data, hosted on GitHub Pages.
- Weekly Trivia Games: Automatically generates new trivia games every week
- 8 Rounds + Final: Each game contains 8 rounds with 3 questions each, plus a Final Trivia question
- Progressive Difficulty: Questions increase in difficulty from easy to expert
- Mobile-Optimized: Responsive design optimized for mobile devices
- Game Tracking: Mark games as played to track your progress
- Node.js 18 or higher
- npm
- Clone the repository:
git clone <repository-url>
cd TriviaBot- Install dependencies:
npm install- (Optional) Set up OpenAI API key for question filtering and rewriting:
export OPENAI_API_KEY=your-api-key-hereIf you don't set the API key, the system will use basic pattern matching to filter questions, but LLM-powered filtering and rewriting will be disabled.
Before generating games, populate the question archive from J! Archive using the Python parser pipeline. This produces cleaner data than the deprecated Node scraper.
# One-time: clone parser, download HTML (parallel), parse into clues.db, then export to archive
npm run populate-archive
# Or run steps manually: after clues.db exists,
npm run db-to-archiveDownload uses a parallel script (default 3 workers, 1.5s between requests, gzip) to avoid overloading j-archive.com. To tune: run the script directly with -w and --delay (e.g. data/jeopardy-parser/.venv/bin/python scripts/download-archive-parallel.py -d data/jeopardy-parser -w 4 --delay 1). Use higher values only if the site is clearly fine with it; aggressive rates can lead to connection refused or IP blocks.
This writes data/archive-backup.json from data/clues.db (created by iamsix/jeopardy-parser and scripts/parser-with-memory.py).
If your archive was ever from the old scraper and you want clean parser-only data, delete it and re-parse:
rm data/archive-backup.json
npm run populate-archiveTo re-download and re-parse everything from scratch (e.g. fresh clues.db), also remove the parser output and DB, then run populate again:
rm -rf data/archive-backup.json data/clues.db data/jeopardy-parser
npm run populate-archiveFamily Feud questions come from ProtoQA. To populate data/family-feud-questions.json:
# Download dev set from GitHub and convert (~979 questions)
node scripts/convert-protoqa-to-family-feud.js --fetch
# Or use a local clone: pass a path to get more questions (e.g. train set)
node scripts/convert-protoqa-to-family-feud.js ./protoqa-data/data/train/train.jsonlUsing train.jsonl (e.g. after cloning protoqa-data) gives many more questions than the default dev set.
# Generate game for today
npm run generate
# Generate game for a specific date
npm run generate -- --date 2024-01-15After generating games, update the index:
npm run update-indexTo avoid differences between your environment and production (styling, icons, fetch):
- Use the dev server:
npm run devthen open the app at the URL it prints (e.g.http://localhost:3000). Avoid openinggame.htmlorindex.htmlviafile://so paths and CORS match production. - Assets: CSS/JS are served as-is (no build step). Production uses the same files; Vercel sets short cache headers for
/css/*and/js/*so deploys propagate. - Icons: Lucide is pinned to a specific version in the HTML so local and production use the same icon set.
Some rounds use special data or LLM generation instead of the main archive:
- Round 2 (Over/Under): LLM-generated numeric questions. Requires
OPENAI_API_KEY. When the key is missing, generation falls back to pool files if available. - Round 4 (List Round): One question with multiple answers, loaded from
data/list-round-questions.json(not from the J! Archive backup). Add or edit that file to change list-round content.
TODO: Document pool sources (over-under, game-show, mixing-things-up) and operator options for rounds 2 and 4 in more detail.
TriviaBot/
├── index.html # Game list page
├── game.html # Game display page
├── css/
│ └── styles.css # Mobile-optimized styles
├── js/
│ ├── game-list.js # Game list logic
│ └── game-display.js # Game rendering logic
├── data/
│ ├── archive-backup.json # J! Archive data (from parser: populate-archive + db-to-archive)
│ ├── games/
│ │ ├── index.json # Games index
│ │ └── game-YYYY-MM-DD.json # Generated games
│ └── played-status.json # Played status tracking
├── scripts/
│ ├── populate-archive.sh # Clone parser, run download + parse → clues.db
│ ├── db-to-archive.js # clues.db → archive-backup.json
│ ├── scrape-archive.js # (Deprecated) J! Archive scraper
│ ├── generate-game.js # Game generator
│ └── update-games-index.js # Index updater
└── .github/
└── workflows/
└── weekly-game.yml # GitHub Actions automation
The project is configured to deploy to Vercel, which hosts both the static site and the serverless function:
-
Import your GitHub repository to Vercel:
- Go to https://vercel.com
- Click "Add New" → "Project"
- Import your GitHub repository
- Vercel will auto-detect the settings
-
Set environment variables:
- In Vercel dashboard → Your project → Settings → Environment Variables
- Required:
GITHUB_TOKEN- Your Personal Access Token (withreposcope) - Optional:
GITHUB_OWNER- Your GitHub username (defaults to 'mordechaih')GITHUB_REPO- Repository name (defaults to 'TriviaBot')API_ENDPOINT- Custom API endpoint URL (if not set, uses VERCEL_URL automatically)
Note:
js/config.jsis automatically generated at build time from these environment variables, so you don't need to create it manually. -
Deploy:
- Vercel will automatically deploy on every push to
main - Your site will be available at
https://your-project.vercel.app - When are changes on production? Production runs whatever is deployed from this repo. If you deploy from Vercel linked to GitHub, that is usually the latest
mainafter each push. Uncommitted or unpushed changes (e.g. inscripts/generate-game.js) are not on production until you commit, push tomain, and Vercel finishes the deploy.
- Vercel will automatically deploy on every push to
If you prefer GitHub Pages instead:
- Push the repository to GitHub
- Enable GitHub Pages in repository settings (Settings > Pages)
- Select
GitHub Actionsas the source - The site will be available at
https://<username>.github.io/TriviaBot
The GitHub Actions workflow (weekly-game.yml) automatically:
- Runs every Monday at 9:00 AM UTC
- Generates a new trivia game
- Updates the games index
- Commits and pushes changes
To manually trigger:
- From the webapp: Click the "Generate New Game" button on the main page
- From GitHub: Go to Actions tab in GitHub, select "Generate Weekly Trivia Game", click "Run workflow"
The webapp includes a "Generate New Game" button that allows you to trigger game generation remotely. This uses a secure serverless function to protect your GitHub token - the token never leaves the server and is never exposed in the browser.
-
Deploy the serverless function (choose one):
Option A: Vercel (Recommended)
- Vercel CLI is already installed locally in the project
- In your project directory, run:
npm run deploy(ornpx vercel) - Follow the prompts to deploy
- After deployment, go to your Vercel dashboard
- Navigate to your project → Settings → Environment Variables
- Add these environment variables:
GITHUB_TOKEN- Your GitHub Personal Access Token (withreposcope)GITHUB_OWNER- Your GitHub username (optional, can be in config.js)GITHUB_REPO- Repository name (optional, can be in config.js)
- Copy your function URL (e.g.,
https://your-app.vercel.app/api/trigger-workflow)
Option B: Netlify
- Install Netlify CLI:
npm i -g netlify-cli - In your project directory, run:
netlify deploy --prod - Follow the prompts to deploy
- After deployment, go to your Netlify dashboard
- Navigate to Site settings → Environment variables
- Add the same environment variables as above
- Copy your function URL (e.g.,
https://your-app.netlify.app/.netlify/functions/trigger-workflow)
-
Create a GitHub Personal Access Token (for the serverless function):
- Go to https://github.com/settings/tokens
- Click "Generate new token (classic)"
- Give it a name like "TriviaBot Game Generation"
- Select the
reposcope - Click "Generate token" and copy it
- Add it as
GITHUB_TOKENin your serverless function's environment variables
-
Configure the webapp:
- Edit
js/config.jsand fill in:const GITHUB_CONFIG = { owner: 'your-username', repo: 'TriviaBot', branch: 'main', apiEndpoint: 'https://your-app.vercel.app/api/trigger-workflow' };
- Important:
js/config.jsis gitignored and won't be committed - Security: The token is stored server-side in your hosting platform, never in the browser
- Edit
- Frontend: Calls your serverless function (no token exposed)
- Serverless function: Uses the token (stored as environment variable) to trigger the workflow
- Workflow: Uses
GITHUB_TOKEN(automatically provided by GitHub Actions) for all operations - Security: Your PAT is stored server-side and never exposed to the browser
- Click "Generate New Game" on the main page
- The workflow will be triggered securely via your serverless function
- You'll see a status message confirming the trigger
- The page will auto-refresh after 30 seconds to check for the new game
- Note: Game generation takes a few minutes to complete
This site uses content from J! Archive, a fan-created archive of Jeopardy! games. The Jeopardy! game show and all elements thereof are the property of Jeopardy Productions, Inc. This website is not affiliated with, sponsored by, or operated by Jeopardy Productions, Inc. This site is for non-commercial, educational, and entertainment purposes only.
- The scraper respects J! Archive's robots.txt and rate limits
- Use
--testflag for development/testing - Scraped data is stored locally and not re-scraped
- Questions are selected based on difficulty scores
- Difficulty is calculated from dollar values and round types
- Questions are tracked to avoid duplicates across games
- Question Filtering: Questions are automatically filtered to remove:
- Anagram and wordplay categories that don't work for pub trivia
- Category-themed questions (e.g., "I'm in Sephora without my glasses")
- Question Rewriting: Category-themed questions are rewritten to focus on content rather than the category theme
- Uses OpenAI GPT-4o-mini for intelligent filtering and rewriting (if API key is set)
- Played status is stored in
localStorageby default - For GitHub storage, you would need to implement GitHub API integration
- The current implementation uses localStorage as a fallback
This project is for educational and non-commercial use only.