Automates the nightly reservation of a 10 person library study room at ICESI
(https://banner9.icesi.edu.co/ic_reservas), 08:00 to 20:00 Monday to Friday,
using Playwright. A small status page lives at
reservation.getcuria.us.
The portal caps each account at 2 hours per booking, so the day is split into six 2 hour blocks and each block uses a different account from a rotation pool.
- GitHub Actions fires at
23:59 America/Bogota(59 4 * * *UTC). main.pycomputes the next weekday (weekends roll forward to Monday), splits 08:00 to 20:00 into six 2 hour blocks, and logs in per block with a different account.- Each block fills the
addReserveform, screenshots before and after, and submits. One failing block never aborts the day, and confirmed blocks remain booked even when other blocks fail. - Screenshots and
status.jsonare uploaded as artifacts and the status page is redeployed with the fresh data.
- Automation: Python 3.11 + Playwright (
main.py,core/*). - Status page: Vite + React + Tailwind v4 + motion + lucide-react
(
apps/web), deployed to Cloudflare as thereservationWorker with thereservation.getcuria.uscustom domain (wrangler.jsonc).
python -m venv .venv && source .venv/bin/activate
pip install -r requirements-dev.txt
playwright install chromiumAdd credentials (gitignored), then dry run:
cp credentials.example.json credentials.json # then edit it
python main.py --headed --dry-runcredentials.json can be a single { "username", "password" } object or a JSON
array for the rotation. CI reads BANNER_USERS_JSON instead.
- The
addReserveroute is a two step Material UI wizard. Step 1 is the requester info (CONTINUAR); step 2 holds actividad, fecha, horas, número de personas, espacio físico, observación (FINALIZAR). - It must be opened by clicking the
AGREGAR RESERVAcard. Navigating the URL directly, or using the sidebar link, drops the in-memory session and crashes the route. - Activities are: Capacitación, Examen, Examen final, Examen multitudinario,
Práctica de Laboratorio, Reunión, Seminario, Taller. There is no "Study
Session", so the default is
Reunión. Número de personasfilters the room list. With10the portal offersSala de estudio 204BI [Capacidad espacio: 10], the 10 person room. If that room is already booked for a block, the block is reported asunavailable.- Valid reservation hours are Monday to Friday 07:00 to 21:00.
- Each user can hold only one 2 hour block per day, so six blocks need six distinct accounts. The runner never reuses an account within a run and frees one back to the pool when its block is unavailable.
FINALIZARonly validates. A confirmation modal then appears andCONFIRMARis what creates the reservation, after which the portal shows "Tu reserva ha sido registrada con éxito".
ruff check . && ruff format --check .
pytest -q
pnpm --filter web typecheck
pnpm --filter web buildpnpm install
pnpm --filter web build
pnpm exec wrangler deploy # needs CLOUDFLARE_API_TOKENSee docs/SPEC.md, docs/ARCHITECTURE.md, and docs/DESIGN.md for details.
Secrets: BANNER_USERS_JSON (six accounts, JSON array), CLOUDFLARE_API_TOKEN.
Variables: RESERVATION_ROOM, RESERVATION_ACTIVITY.
The portal issues ConstanciaDeReservaDeEspacio.pdf after CONFIRMAR. This is
the official receipt to present when collecting the room keys. The dashboard
does not generate or print a substitute PDF.
The automation saves the original download under gitignored bookings/ with
a date and unique filename. CI includes these files in the private
reservation-evidence run artifact. A confirmed booking without a saved PDF
shows Not captured; it is never represented by a placeholder receipt.
If submission confirmation is uncertain, the block shows Needs verification
and is not retried with another account. Verify it in the portal first.
The Worker protects status and receipt routes before serving static assets.
Only verified Google emails in the ALLOWED_EMAILS Worker secret can sign in.
The allowlist is extracted from explicit email addresses in the local Valance
chat export, not from names or inferred addresses. It is not bundled in the UI.
Configure a Google OAuth web client with the exact redirect URI
https://reservation.getcuria.us/auth/callback. Store GOOGLE_CLIENT_ID and
GOOGLE_CLIENT_SECRET as Worker secrets, along with a random SESSION_SECRET.
The STATUS_READ_TOKEN secret also belongs in GitHub Actions for status restore.
It grants read access to status only, not to receipts.
Google authentication uses the authorization code flow with PKCE, signed state, and a 12-hour Secure, HttpOnly session cookie. Removing an email from the allowlist immediately denies its existing session. Missing OAuth configuration keeps private routes closed and displays a configuration message on the login page.
References: Google OpenID Connect
and Cloudflare asset routing.
Run node --test worker/auth.test.mjs for the authorization checks.