A WhatsApp chatbot that automatically tracks ultimate frisbee game scores in real-time, with a web interface for live game updates.
-
Natural Language Processing: Recognizes score updates from casual chat messages
- "goal!" → Records a goal for your team
- "Jake to Mason 5-3" → Records goal with players and score
- "Ellis block" → Records defensive block
- "Cyrus steal" → Records defensive steal
- "Timeout Tech" → Records timeout for team
- "Tech starting on O" → Records starting offense/defense
- "halftime" → Records halftime
- "game over" → Ends the game
-
Manual Commands: Explicit commands for precise control
/newgame <our-team> <opponent>- Start tracking a new game/goal [us|them]- Record a goal/halftime- Record halftime/endgame- End game/undo- Undo last event/score- Show current score
-
Live Web Interface: WFDF-style game display at https://score.kcuda.org
- Timeline View: Three-cell layout with colored bars, event details, and scores
- Progression Table: Point-by-point score evolution with visual indicators
- Break Detection: Accurate offensive hold vs break score identification
- Defensive Stats: Display blocks (🛡️) and steals (🏃) in timeline
- Timeout Tracking: Show timeout events with team attribution
- Winner Indicators: Arrows pointing to winning score for finished games
- Game Metadata: Date, time, and field information
- Auto-refreshing every 3 seconds
- Mobile-responsive design
Built as a TypeScript monorepo with three packages:
- @scorebot/shared - Shared types and utilities
- @scorebot/bot - Cloudflare Worker API + WhatsApp client
- @scorebot/web - Web interface (static site)
- Backend: Cloudflare Workers, Durable Objects, D1 (SQLite)
- Frontend: TypeScript, Vite, vanilla HTML/CSS
- WhatsApp: whatsapp-web.js (Node.js client)
- Hosting: Cloudflare Workers & Pages
- Node.js 18+
- npm or yarn
- Cloudflare account (free tier works)
- WhatsApp account
- Clone and install dependencies:
git clone <your-repo-url>
cd scorebot
npm install- Build shared package:
cd packages/shared
npm run build
cd ../..- Set up Cloudflare D1 database:
cd packages/bot
# Create database
npx wrangler d1 create scorebot
# Update wrangler.toml with the database_id from the output
# Run migrations
npx wrangler d1 execute scorebot --local --file=./migrations/0001_initial_schema.sql- Start the Worker locally:
npm run dev
# Worker runs on http://localhost:8787- Set up and start the web interface:
cd ../web
cp .env.example .env
# Edit .env and set VITE_API_URL=http://localhost:8787
npm run dev
# Web interface runs on http://localhost:3000- Set up WhatsApp client:
cd ../bot
cp .dev.vars.example .dev.vars
# Edit .dev.vars and set API_URL=http://localhost:8787
# Start WhatsApp client
npm run whatsapp
# Scan the QR code with WhatsApp on your phone- In your WhatsApp group, send:
/newgame YourTeam OpponentTeam
-
The bot will confirm and start tracking the game
-
Share the web interface URL with your team to follow along
The bot automatically recognizes natural language:
- "Goal!" or "We scored!" → Records a goal for your team
- "Jake to Mason" → Records goal with assist and scorer
- "5-3" → Records the current score
- "Ellis block" → Records defensive block leading to turnover
- "Cyrus steal" → Records defensive steal
- "Timeout Tech" → Records timeout for your team
- "Halftime" → Marks halftime
- "Game over" or "Final" → Ends the game
Or use explicit commands:
/goal→ Goal for your team/goal them→ Goal for opponent
For accurate break vs hold detection, specify starting possession:
- "Tech starting on O" → Indicates team started on offense
- "Tech starting on D" → Indicates team started on defense
The system automatically tracks:
- Offensive holds (scoring when expected)
- Break scores (scoring after defensive turnover)
- Defensive plays (blocks and steals)
- Timeouts by team
Open https://score.kcuda.org to see:
- Live score updates with winner indicators (for finished games)
- WFDF-style timeline with event-by-event breakdown
- Colored bars indicating scoring team (green for your team, orange for opponent)
- Event types: Offensive Hold, Break Score, Halftime, Timeouts
- Defensive play indicators (blocks 🛡️ and steals 🏃)
- Player names for assists and scorers
- Progression table showing point-by-point score evolution
- Bold numbers indicate which team scored
- Underlined scores indicate break scores
- Vertical separators for game start, halftime, and final
- Game metadata: Date, time, and status
- Auto-refreshes every 3 seconds for live updates
cd packages/bot
# Create production D1 database
npx wrangler d1 create scorebot
# Update wrangler.toml with production database_id
# Run migrations
npx wrangler d1 execute scorebot --file=./migrations/0001_initial_schema.sql
# Deploy
npm run deploy
# Deployed to:
# - https://scorebot-api.siener.workers.dev
# - https://api.score.kcuda.org (custom domain)cd packages/web
# Update .env with production Worker URL
echo "VITE_API_URL=https://scorebot-api.siener.workers.dev" > .env
# Build and deploy
npm run build
npm run deploy
# Deployed to: https://score.kcuda.orgThe project uses custom domains for professional URLs:
- API:
api.score.kcuda.org - Web:
score.kcuda.org
See CUSTOM_DOMAIN_SETUP.md for configuration details.
The WhatsApp client needs to run continuously on a server:
# On your server
cd packages/bot
# Set production API URL
echo "API_URL=https://scorebot-api.your-subdomain.workers.dev" > .env
# Install PM2 for process management
npm install -g pm2
# Start with PM2
pm2 start "npm run whatsapp" --name scorebot-whatsapp
pm2 save
pm2 startup # Follow instructions to enable auto-startscorebot/
├── packages/
│ ├── shared/ # Shared types and utilities
│ │ └── src/
│ │ ├── types.ts
│ │ └── utils.ts
│ ├── bot/ # Backend API and WhatsApp client
│ │ ├── src/
│ │ │ ├── api/ # API router
│ │ │ ├── db/ # Database service
│ │ │ ├── durable-objects/ # Game state management
│ │ │ ├── parser/ # Message parser
│ │ │ └── whatsapp/ # WhatsApp client
│ │ ├── migrations/ # Database migrations
│ │ └── wrangler.toml # Cloudflare config
│ └── web/ # Web interface
│ └── src/
│ ├── index.html
│ ├── main.ts
│ └── style.css
├── package.json
└── tsconfig.json
See CLAUDE.md for detailed development guidance.
# Test the API
curl -X POST http://localhost:8787/games \
-H "Content-Type: application/json" \
-d '{"chatId":"test","ourTeamName":"Team A","opponentName":"Team B"}'- Make sure you scanned the QR code within 60 seconds
- Delete
.wwebjs_auth/folder and try again - Check that port 8787 is accessible
- Verify VITE_API_URL is set correctly
- Check browser console for CORS errors
- Ensure Worker is running and accessible
- Check WhatsApp client logs for parsing confidence
- Try using explicit commands instead of natural language
- Verify game was created with
/newgame
MIT
Contributions welcome! Please open an issue or PR.