bash — ~/projects/booking-bot
$cd ~/projects/booking-bot && cat summary.txt
Portal-agnostic web booking and slot-monitoring bot — it logs into an appointment portal, watches for availability matching your criteria, and books the moment it appears. Everything portal-specific lives in a small adapter; session, polling, OTP, notifications and anti-bot handling stay shared.
BOOKING BOT
Automation Engineer · #a91f3c8 automation
Node.js 24 · TypeScript · Playwright · Docker · Telegram
github.com/sleepyhead000/booking-bot
~/projects/booking-bot $
cat README.md
README.md
$cat architecture.txt
BookingRunner (core loop) → PortalAdapter (your site's selectors, page order, API shapes) → Chromium via Playwright. Adapters are auto-registered from src/adapter/<id>/index.ts, so porting to a new portal is one folder and zero core edits. Notifier (console + Telegram) fans out from the same hub.
$cat config.txt
Three config layers with later-wins semantics: config/default.yaml → environment → CLI flags (zod-validated, commander CLI with documented exit codes). Secrets never live in YAML — credentials, proxy and solver keys are read from .env only, and applicant data stays gitignored.
$cat stealth.txt
Four anti-bot layers in order of cost: fingerprint hygiene via a pre-page-load init script, a bot doctor probe suite that runs inside CI, an optional residential proxy per run, and ChallengeGuard for Cloudflare/Turnstile-style interstitials — it waits them out by default or hands the task to a solver provider you register.
$cat otp.txt
The bot never guesses a code: it notifies you on every enabled channel, then waits — type it in the terminal, reply to the Telegram message, or pre-set OTP_CODE for SMS hooks and tests. 180s timeout, 3 attempts before it gives up, and sessions persist 12h so a monitor run does not re-login on restart.
~/projects/booking-bot $
ls -l features/
adapters
Pluggable portals · folder name = adapter id · auto-registered · no core edits
monitoring
Adaptive polling · 4–12s jittered intervals · exponential backoff on empty cycles
stealth
Fingerprint hygiene · webdriver/chrome/plugins stripped · bot doctor probes in CI
challenge
ChallengeGuard · detects interstitials · waits them out or calls a solver provider
otp
Human-in-the-loop · terminal · Telegram reply · env hook — 180s / 3 attempts
notifications
Telegram + console · fan-out hub · approve a booking from your phone
config
3 config layers · YAML → env → CLI · secrets only in .env · zod-validated
docker
Docker Compose · multi-stage node:24 + Chromium · config mounted read-only
testing
Unit + E2E · demo portal driven in-process · stealth regressions fail the build
~/projects/booking-bot $
cat stack.yaml
runtime
Node.js 24 · TypeScript · commander CLI · zod config schema
browser
Playwright (Chromium) · pre-load init scripts · human timing (humanType/humanClick)
modules
adapters · monitor (slot filter, XHR sniffer) · otp · notify · solver · documents
anti-bot
fingerprint hygiene · bot doctor · ChallengeGuard · proxy support · solver interface
infra
Docker Compose · lockfile (no double-booking) · session storage · redacted logs
testing
Vitest + Playwright E2E · lint · typecheck · CI doctor assertion
~/projects/booking-bot $
git log --oneline
#4f8a2d1feat/adapters
Portal adapter seam
TypeScript · PortalAdapter contract · auto-registered adapters
- Defined a PortalAdapter contract (login, isLoggedIn, isOtpPromptVisible, submitOtp, getSlots, selectSlot, fillApplication, attachDocuments, submitApplication) — porting a portal is copying the reference folder, not editing the core.
- getSlots runs every poll cycle, so it prefers the portal's own XHR via an API sniffer that captures the JSON, falling back to a DOM scrape only when there is no API.
- Wrote a porting checklist: page order with challenge waits, slot metadata for re-selection, human typing/clicking, BotError categories for retry, and verified file uploads — the most common way a booking dies at the last step.
#9c3e7b5feat/stealth
Stealth surface & bot doctor
Playwright init scripts · challenge guard · CI probe
- A pre-page-load init script strips the cheap tells — navigator.webdriver, missing window.chrome, empty plugins and languages — and pins a real Chrome UA for the exact Chromium build in use.
- bot doctor probes the whole surface against a live browser and prints pass/fail; the E2E suite asserts it, so a stealth regression fails CI after any Playwright upgrade.
- ChallengeGuard refuses to click through an interstitial: it notifies you, waits it out (default), or delegates to a solver provider you implement in src/solver/ — only the interface ships, no paid solver wired in.
#2b71e40feat/monitor
Adaptive poller, lockfile & sessions
slot filter · jittered backoff · lockfile · session store
- Poll interval defaults to 4–12s with jitter and exponential backoff on empty cycles, so the traffic never looks like a metronome.
- A lockfile stops two instances booking the same slot, and sessions persist for 12h so a restarted monitor does not re-authenticate every time.
- Documented exit codes (0 success · 2 nothing matched · 64 bad config · 130 cancelled) make the bot composable in scripts and cron.
#e50d1a9test/e2e
Demo portal & E2E suite
Vitest · Playwright · bundled demo portal · Docker
- A bundled demo portal (login → OTP → slots that trickle in → file upload → confirmation) lets the whole pipeline run and regression-test without touching a real site.
- The suite drives the real core in-process: session restore, OTP, adaptive polling, slot selection, form fill, PDF attach and submit — plus a second run with a fake anti-bot overlay that it must wait out.
- Ships Docker Compose (multi-stage node:24-bookworm-slim + Chromium) with config mounted read-only, so config changes never need a rebuild.