Skip to content

Repository files navigation

Bunkforces

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.

monochrome UI

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.


What it does

  • 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).


Architecture (and where the "API key" fits)

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).


Run it

Option A — with the backend (recommended: embeds full statements)

cd backend
pip install -r requirements.txt
uvicorn app:app --port 8000

Then open http://localhost:8000. The frontend auto-detects the backend and enables full-statement PDFs.

Option B — frontend only (no Python)

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).

Option C — the desktop app

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.py

Inside 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 (Railway / any host)

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.tomlforces the Python provider (providers = ["python"]). This is the important one: without it Nixpacks sees index.html at 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.txt
  • Procfile / 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:

  1. 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 up nixpacks.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.
  2. The frontend only ever calls the API on its own origin (relative URLs) — it never uses localhost unless the page was opened from file:// or on localhost itself.

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>

If Cloudflare blocks statement fetches

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:

  1. Log in to codeforces.com in your browser.
  2. Open DevTools → Network → any codeforces.com request → copy the Cookie request header value (contains JSESSIONID, cf_clearance, etc.).
  3. 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.txt containing the cookie string.

Fetched statements are cached in backend/cache/ so you only pay the fetch cost once per problem.


Project layout

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

Typical flow

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.

Notes

  • The problem list is cached in localStorage for 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.

About

an utility over codeforces problemset for proper time usage ;)

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages