Pick problems. Print them. Bunk class productively.
A local-first web tool that pulls random Codeforces problems matching your filters, typesets them into a compact print-ready PDF you can carry to class, and lets you jot per-problem notes that persist in your browser so you can implement solutions at home.
A strictly two-colour (black & white) UI in the Ubuntu / Nerd Font typeface, with square corners. Filters sit on top and stay fully visible; only the problem list scrolls. Dark mode is a pure inversion.
- Problem picker — pick n random problems, filtered by rating range, division (Div 1–4 / Educational), problem index range (A→Z), tags (match any/all), minimum solve count, and solved/unsolved (via your CF handle). Reroll individual problems, dedup guaranteed.
- PDF typesetter — clean, ink-friendly, single-column print-to-PDF with adjustable font size, margins, header-only vs. full statement, sample tests, and ruled/dotted/blank scratch space under each problem. Each problem gets a header with contest, index, rating, tags, direct URL and a QR code, and every page carries the footer "Set generated by Bunkforces, built by Aritro "sortira" Shome".
- Notes & local storage — per-problem notes and status (To-do / Thinking / Solved-on-paper / Implemented), saved named problem sets, and JSON export/import. Everything lives in your browser — no account.
Math in statements is rendered with MathJax (both modern $$$…$$$ and legacy
Codeforces markup).
Bunkforces is client-first. The frontend talks to the public Codeforces API directly from your browser — no API key needed — for:
- the problem list + solve counts (
problemset.problems), - division names (
contest.list), - your solved problems (
user.status?handle=…).
The only thing a browser can't do is read full problem statements: Codeforces problem pages sit behind Cloudflare and send no CORS headers. That one job is handled by an optional tiny FastAPI backend that fetches and parses statements from your own machine/IP.
The tool is 100% usable without the backend — you just get rich header + QR cards instead of embedded statements (scan the QR in class to read online).
cd backend
pip install -r requirements.txt
uvicorn app:app --port 8000Then open http://localhost:8000. The frontend auto-detects the backend and enables full-statement PDFs.
Serve the project root with any static server and open it:
python -m http.server 5500 # then open http://localhost:5500…or just open index.html directly. Everything works except embedded
statements (PDF falls back to header + QR cards).
apps/desktop is a native window (pywebview → Edge WebView2 /
WebKit) wrapped around a local server, serving this same frontend untouched.
Download Bunkforces.exe from the
latest release — no
install, no Python — or run it from source:
cd apps/desktop && pip install -r requirements.txt && python bunkforces_desktop.pyInside it, bunkforces_core is a
standard-library-only implementation of the same /api/* protocol as the backend
(so PDFs get full statements), plus a disk cache for the Codeforces API and an
injected shell bar: prefetch statements, cookie entry, cache management, notes
mirror, and a QR code that hands the running app to your phone (--lan). None of
it touches the web pipeline below.
Deploy the FastAPI app — it serves the frontend and the API from the same
origin, so the browser talks to /api/... relatively (no hardcoded hosts).
This repo already contains what Railway needs at the root:
nixpacks.toml→ forces the Python provider (providers = ["python"]). This is the important one: without it Nixpacks seesindex.htmlat the root and deploys the repo as a static site, so the FastAPI backend never runs and full statements show "no backend". Forcing Python runs the API.requirements.txt→-r backend/requirements.txtProcfile/railway.json→ start command:uvicorn backend.app:app --host 0.0.0.0 --port $PORT
Point Railway at the repo and deploy. Railway injects $PORT; the app binds to
it and serves the frontend and /api/* from the same origin.
If it still says "no backend" after deploying:
- Visit
https://<your-app>.up.railway.app/api/health— it must return{"ok":true}. If instead you get your page's HTML or a 404, Railway is still serving static files: redeploy so it picks upnixpacks.toml(or delete and recreate the service if it cached the static builder). Make sure the new files are committed and pushed to the branch Railway builds. - The frontend only ever calls the API on its own origin (relative URLs) —
it never uses
localhostunless the page was opened fromfile://or onlocalhostitself.
Because Railway runs from a datacenter IP, Codeforces' Cloudflare may block
statement fetches ("reason": "blocked"). If so, set your CF session cookie as
an env var so requests use your identity (see below):
BUNKFORCES_CF_COOKIE = <your codeforces.com Cookie header>
Codeforces problem pages are behind Cloudflare's bot check. From your own home
connection the backend usually gets through with normal browser headers. If you
see "available": false, "reason": "blocked", hand the backend your logged-in
Codeforces session so requests use your browser identity — this is the
"API key" equivalent:
- Log in to codeforces.com in your browser.
- Open DevTools → Network → any codeforces.com request → copy the
Cookierequest header value (containsJSESSIONID,cf_clearance, etc.). - Provide it to the backend either way:
- env var:
set BUNKFORCES_CF_COOKIE=<paste>(Windows) /export BUNKFORCES_CF_COOKIE=<paste>(macOS/Linux), or - a file
backend/cf_cookie.txtcontaining the cookie string.
- env var:
Fetched statements are cached in backend/cache/ so you only pay the fetch
cost once per problem.
bunkforces/
├── index.html # single-page app
├── about.html # separate About page
├── fonts/ # vendored Ubuntu woff2 (offline)
├── css/
│ ├── style.css # monochrome screen UI
│ └── print.css # clean B&W print / PDF layout
├── js/
│ ├── cf.js # Codeforces data layer (fetch, cache, filter, pick)
│ ├── storage.js # localStorage: notes, statuses, saved sets
│ ├── pdf.js # print/PDF builder + MathJax + QR
│ ├── app.js # UI controller
│ └── qrcode.js # vendored QR generator (MIT, offline)
├── backend/
│ ├── app.py # FastAPI: static serving + statement fetch/parse
│ └── requirements.txt
├── apps/
│ └── desktop/ # native app: pywebview window + PyInstaller spec
│ └── bunkforces_core/ # stdlib server, statement parser, shell bar
└── README.md
Set filters → Generate Set → reroll/remove to taste → tweak PDF options → Download / Print PDF → print → solve on paper in class + write notes at home → mark Implemented. Save the set as a named session to revisit it later.
- The problem list is cached in
localStoragefor 24h (refreshes in the background), so the picker works offline once loaded. - Data belongs to Codeforces; this tool just organizes and prints it for personal study.