Selenium bot that auto-books Goethe Institut exam slots for Pakistan region. Web control panel (Flask) + dashboard frontend. Students loaded from Google Sheets or SQLite DB.
# Deploy backend to Railway (auto-deploys from GitHub main; manual)
railway up -d C:\Users\brosp\Downloads\goethe-bot
# Deploy frontend to Netlify
npx netlify deploy --prod --dir=frontend --site 0d638a6c-008a-402b-b8a9-7f6a4541980e
# or from project dir:
npx netlify deploy --prod --dir=frontend
# Set Railway env var
railway variable set KEY=VALUE
# Check Railway logs
railway logs --service goethe-booking-bot -n 100
# Trigger Railway redeploy
railway service redeploy --yes
# or: git commit --allow-empty -m "redeploy" && git push origin main| Service | URL |
|---|---|
| Frontend | https://snazzy-kleicha-1d59fd.netlify.app |
| Backend | https://goethe-booking-bot-production-21af.up.railway.app |
| GitHub | https://github.com/abeermeer/goethe-booking-bot (public, new home) |
| Old GitHub (fork) | https://github.com/hamzabot655/goethe-booking-bot (private fork, may be stale) |
- Auth login: AUTH_EMAIL=
hamzarafiq655@gmail.com/ AUTH_PASSWORD=Hamza@123(Railway env vars) - ScrapingBee API: RQS2P8K2SVGO15HH5X9C17P006KS9RCULD3M3X4URWZ7XEPVFU4GMG5B3U6DK2470PUG0XREZKVKSN3P (set via
SCRAPINGBEE_API_KEYenv var) - Google Sheet ID:
1C7VD_52VnGmJqYSQGtdNzBZGekvCRHWUrdZCgTvvhAY(GOOGLE_SHEET_IDenv var) - Google Service Account: Base64 in
GOOGLE_SERVICE_ACCOUNT_B64env var
- Project: hospitable-heart (ID: 520adb72-b1f4-4021-8c4b-21ca81f8a901)
- Service: goethe-booking-bot (ID: f568e242-4d2a-4b44-8205-07899abfbd26)
- Environment: production (ID: 20945f76-1cfa-4e38-b50b-a5cb8d5f47cd)
- Region: sfo
- Databases: Postgres-ZHgW, Postgres, Postgres-3Fwo (unused by the main app; uses SQLite)
| File | Purpose |
|---|---|
webapp.py |
Flask backend — API endpoints, CORS, auth, bot control |
booking_helper.py |
Core Selenium bot — login, 5-step wizard, smart_retry, polling |
goethe_scraper.py |
Pakistan exam schedule scraper — ScrapingBee → curl_cffi → Playwright → fallback |
google_sheets.py |
Google Sheets integration — read/write students, auto-fill dates, update schedule tab |
db.py |
SQLite DB — students, logs, settings tables |
frontend/index.html |
Single-page dashboard — 6 sections (Dashboard, Controls, Schedule, Students, Logs, Settings) |
Dockerfile |
Railway deployment — Python + Chrome + Playwright |
pk_fallback.json |
Offline exam schedule data (10 entries, Jul-Oct 2026) |
netlify.toml |
Netlify config — publishes frontend/ directory |
| Endpoint | Method | Auth | Description |
|---|---|---|---|
/login |
POST | No | Returns JWT token |
/students |
GET | Yes | List all students (DB + sheet + config merged) |
/students |
POST | Yes | Add student to DB + Google Sheet |
/students/<id> |
DELETE | Yes | Delete DB student (negative id = sheet-only, returns 400) |
/goethe-schedule |
GET | Yes | Pakistan exam schedule (?refresh=1 for live, ?level=A1 to filter) |
/sheets/update-schedule |
POST | Yes | Update Google Sheets Schedule tab from Goethe data |
/sheets/auto-fill |
POST | Yes | Auto-fill booking_datetime for empty students |
/schedule |
GET | Yes | Load students from Google Sheet |
/start |
POST | Yes | Start bot on all students |
/stop |
POST | Yes | Stop bot |
/config |
GET/POST | Yes | Bot configuration |
/live-status |
GET | Yes | Live booking status per student |
/heartbeat |
GET | Yes | Dead man switch heartbeat |
- DB students: positive
id(SQLite auto-increment) - Sheet/config students: negative
id(e.g., -1, -2) assigned in_get_loaded_students() - Delete button visible for all, but sheet-only delete returns error msg
- Load students (DB + Sheet merged via
_get_loaded_students()) - Each student gets own
threading.Thread+ own Chrome browser (parallel) - For each student: navigate to level URL → wait/poll for booking button → CAS login → 5-step wizard
- Wizard steps: Personal Data 1 → Personal Data 2 → Payment (Invoice) → Promo Code → Review & Confirm
- Status pushed via WebSocket to frontend live dashboard
- ScrapingBee (premium_proxy=true) — primary, ~15s per 3 levels parallel
- curl_cffi (chrome131 impersonate) — fallback if ScrapingBee fails
- Playwright (headless Chromium) — fallback if curl_cffi unavailable
- pk_fallback.json — last resort offline data
_retry_gsheet(): 5s → 10s → 20s → 40s exponential backoff- 15s TTL in-memory cache on
load_sheet_data()to reduce read frequency strict=Falseon data validation dropdown to avoid red dot on existing values
Cause: Student missing id field → URL /api/students/undefined → HTML 404
Fix: All students now get an id (positive for DB, negative for sheet/config). Already fixed.
Cause: Google Sheets 60 reads/min/user limit exceeded Fix: Retry with backoff + 15s cache. If persistent, wait 1 min.
Cause: ScrapingBee monthly limit, Playwright browsers not installed, or Goethe API blocking
Fix: Fallback to pk_fallback.json. If ScrapingBee exhausted, replace API key.
Reality: ~1.5-2 min per student when booking open. Parallel for multiple students (same total time).
- Railway auto-deploys from GitHub
mainbranch pushes - Railway env vars are picked up on next deploy (not hot-reloaded)
- Netlify deploys are manual via CLI (
npx netlify deploy --prod --dir=frontend) - Frontend is pure HTML/CSS/JS — no build step needed
- Backend SQLite DB resets on each Railway deploy (data persists only in Google Sheet or GitHub config)
- India adaptation: change
/ins/pk/to/ins/in/, addundetected-chromedriver, Indian proxies - Google Sheets append_student doesn't retry on 429
- Config file students lack DB ids (can't be deleted via API)
- No automated tests for the booking flow (only scrapers and helpers tested)