# ani-web Web front-end for the [ani-cli](https://github.com/pystardust/ani-cli) workflow: search, browse, and stream anime in the browser via anidb.app. ## Quick start ```sh uv venv .venv uv pip install --python .venv/bin/python -r requirements.txt .venv/bin/python -m server.credentials # set the login password (one-time) ./run.sh ``` Open . `run.sh` restarts cleanly if the server is already running; logs go to `server.log`. The server refuses to start without a password. ## Features - Login: single shared password, 30-day session cookie, 5 wrong attempts → 15-min lockout. PBKDF2-SHA256 hash + session secret in `server/data/credentials.json` (0600). - Search with poster grid, detail view with synopsis + season tabs - HLS playback (vendored hls.js), browser ABR across 1080/720/360p - Dub/sub toggle - Autoplays the first episode (or resumes the last watched one); if the browser blocks autoplay, a "click to play" overlay appears - Server-persisted watch history (`server/data/history.json`): watch position saved every ~10s, resume where you left off, top-5 "recently played" on the home page — shared across browsers and devices ## Layout ``` server/ FastAPI app (app.py) + anidb.app fetchers (upstream.py) upstream.py is the ONLY Cloudflare-facing code static/ front-end: index.html, app.js, style.css, hls.min.js (vendored) run.sh launch/restart the server SPEC.md design + the verified anidb.app data flow ``` ## Dev notes - anidb.app is behind Cloudflare; all upstream requests use `curl_cffi` Chrome TLS impersonation. If that ever stops working, everything is isolated in `server/upstream.py`. - The video CDN sends no CORS headers, so playback goes through the backend HLS proxy (`/stream/{token}`), which rewrites playlist URIs. - `python server/upstream.py` runs a self-check of the fetchers (search → details → episodes → dub stream for Chainsaw Man). - Episode titles are not available: anidb's episode API only returns `{id, number, number2, filler}`. Adding titles needs a free MyAnimeList API key.