- shared thread-local curl_cffi sessions (no TLS handshake per call) - in-memory TTL cache: search 5min, details/episodes/languages/ playlist-URLs 1h; api_stream retries once with a cold cache on 502 (recovers rotated playlist URLs) - /stream LRU (24 entries) serves re-seeks and playlist re-fetches from memory - hls.min.js: Cache-Control public max-age=30d (was no-store -> 500KB re-download per page load on mobile) Measured (click->play chain, cold -> warm): search 0.45s -> 0.00s detail 0.46s -> 0.00s episodes 0.40s -> 0.00s stream 1.06s -> 0.00s m3u8 x2 0.64s -> 0.00s segment 0.20s -> 0.00s
83 lines
3.7 KiB
Markdown
83 lines
3.7 KiB
Markdown
# 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.users add <you> # create your account (one-time)
|
|
./run.sh
|
|
```
|
|
|
|
Open it through your HTTPS proxy (or <http://localhost:8321> from the
|
|
host itself — browsers treat localhost as trusted). `run.sh` restarts
|
|
cleanly if the server is already running; logs go to `server.log`.
|
|
With `ANIWEB_SECURE=1` (set by `run.sh`), the session cookie is
|
|
`Secure`, so remote plain-HTTP access can't hold a session — only the
|
|
HTTPS proxy works. A legacy `credentials.json` password, if any,
|
|
is migrated to user `admin` on first start.
|
|
|
|
Account management (no self-signup by design):
|
|
|
|
```sh
|
|
.venv/bin/python -m server.users add <name> # add user (prompted password)
|
|
.venv/bin/python -m server.users rm <name> # remove user + their history
|
|
.venv/bin/python -m server.users list # list users
|
|
```
|
|
|
|
Security: passwords are PBKDF2-SHA256 (210k iters, per-user salts) in
|
|
`server/data/users.json` (0600). Sessions are signed 30-day `HttpOnly`
|
|
`SameSite=Lax` cookies; `run.sh` sets `ANIWEB_SECURE=1` so cookies are
|
|
`Secure` and only work through your HTTPS proxy. Accounts deleted via
|
|
the app or CLI are rejected immediately, even with an old cookie.
|
|
|
|
## Features
|
|
|
|
- Per-user accounts (CLI-created, no self-signup): 30-day signed
|
|
session cookie, 5 wrong attempts → 15-min lockout per user.
|
|
PBKDF2-SHA256 hashes in `server/data/users.json` (0600).
|
|
- Per-user watch history (`server/data/history/<user>.json`): position
|
|
saved every ~10s, resume where you left off, top-5 "recently played"
|
|
on the home page. Nothing stored in the browser — new devices pick
|
|
up your watch state after login, isolated per account.
|
|
In-header chip shows the user, with logout + delete-account.
|
|
- Search with poster grid, detail view with synopsis + season tabs
|
|
- HLS playback (vendored hls.js), browser ABR across 1080/720/360p
|
|
- Dub/sub toggle
|
|
- Fast repeat navigation: upstream data is TTL-cached and HLS
|
|
playlists/segments are LRU-cached, so re-opening a show or re-seeking is
|
|
near-instant (first load still pays the upstream round-trips)
|
|
- Autoplays the first episode (or resumes the last watched one); if the
|
|
browser blocks autoplay, a "click to play" overlay appears
|
|
|
|
## Layout
|
|
|
|
```
|
|
server/ FastAPI app (app.py), anidb.app fetchers (upstream.py),
|
|
accounts (users.py). upstream.py is the ONLY Cloudflare-facing code
|
|
static/ front-end: index.html, login.html, app.js, style.css, hls.min.js (vendored)
|
|
tests/ test suite (run.py orchestrator, HTTP + browser tests)
|
|
run.sh launch/restart the server (sets ANIWEB_SECURE=1)
|
|
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).
|
|
- `python tests/run.py` runs the full test suite against a throwaway
|
|
server (:8399, temp data dir — never touches the real data). Browser
|
|
tests need playwright (`pip install playwright && playwright install
|
|
chromium`); they're skipped if absent.
|
|
- Episode titles are not available: anidb's episode API only returns
|
|
`{id, number, number2, filler}`. Adding titles needs a free
|
|
MyAnimeList API key.
|