docs: sync README + SPEC with the shipped reality

Both docs still described the pre-provider-layer single-source app:

- README: anidb-only framing, missing stats page, 'upstream.py is the
  ONLY Cloudflare-facing code', and a self-check command that does not
  work (plain script execution breaks the providers' relative imports;
  the asserts are anidb-specific under the hianime-first default) —
  now documents ANI_SOURCE=anidb + the -m form. Dropped the stale
  'episode titles are not available' note (Jikan titles shipped).
- SPEC: 'no database' -> SQLite, architecture -> 3 providers, full
  endpoint list (intros/searches/events/stats/cdn, DELETE history,
  ?all=1, {sub}), auth hardening (per-IP budget, X-Aniweb on /login),
  tests/run.py -> pytest + ANIWEB_LIVE_TESTS gating, out-of-scope list
  (countdown/intro-skip/titles shipped), risks updated for the
  fallback layer, milestone 8 pointer.
This commit is contained in:
jozamudi committed 2026-09-16 21:14:26 +00:00
1 parent 4aca78cab8
commit 0f6bd0a52a
2 files changed
+110 -53

No files matched your search

+32 -21
View File
@@ -1,9 +1,9 @@
# ani-web
Web front-end for anime streaming: search, browse, and stream anime in
the browser via anidb.app. Multi-user, with per-user watch history and
HLS playback proxied through the backend. Tuned for phone as much as
desktop.
the browser (hianime.at primary; anidb.app and anikototv.to as
fallbacks). Multi-user, with per-user watch history and HLS playback
proxied through the backend. Tuned for phone as much as desktop.
![login](docs/img/login.png)
@@ -66,10 +66,11 @@ relative timestamps.
### Search
The header search box queries anidb.app and shows results as a poster
grid. Your last 8 searches (per user) appear in a dropdown under the
box, filter live as you type, and click to re-run. Results open the
detail view: synopsis, stats, season tabs, and filler badges.
The header search box queries the active source and shows results as a
poster grid. Your last 8 searches (per user) appear in a dropdown under
the box, filter live as you type, and click to re-run. Results open the
detail view: synopsis and season tabs (where the source provides
them), plus filler badges.
![search](docs/img/search.png)
@@ -106,11 +107,20 @@ stall playback.
![detail](docs/img/detail.png)
### Stats
`?stats` (hamburger menu) — watch minutes, episodes started/completed,
active days + best streak, sub/dub split, weekday and per-day heatmaps,
top-10 shows, top searches. Derived server-side from the position-event
stream (`POST /api/events`), 30–365 day windows.
## Layout
```
server/ FastAPI app (app.py), anidb.app fetchers (upstream.py),
accounts (users.py). upstream.py is the ONLY Cloudflare-facing code
server/ FastAPI app (app.py), source providers (providers/: hianime,
anidb, anikoto) over shared HTTP + dispatch (upstream.py),
accounts (users.py). upstream.py + providers/ are the ONLY
Cloudflare-facing code
static/ front-end: index.html, login.html, app.js, style.css, hls.min.js (vendored)
tests/ pytest suite: conftest.py (throwaway server), HTTP + browser tests
docs/img/ README screenshots
@@ -120,17 +130,21 @@ 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 upstream sites sit behind Cloudflare; all upstream requests use
`curl_cffi` Chrome TLS impersonation. Sources are ordered by
`ANI_SOURCE` (default `hianime,anidb,anikoto`) — a dead one falls
through to the next. All upstream-facing code is isolated in
`server/upstream.py` + `server/providers/`.
- The video CDN sends no CORS headers, so playback goes through the
backend HLS proxy (`/stream/{token}`), which rewrites playlist URIs.
- `.venv/bin/python server/upstream.py` runs a self-check of the
fetchers (search → details → episodes → dub stream for Chainsaw Man).
- A background probe fetches 64KB from the CDN every 3 min and logs
state flips to `server.log`; `GET /api/cdn` (public) returns the
latest throughput + ~1h history. Use it to check if "playback
broken" is the CDN again.
- `ANI_SOURCE=anidb .venv/bin/python -m server.upstream` runs a live
self-check of the anidb fetchers (search → details → episodes → dub
stream). Must be the `-m` form — plain script execution breaks the
providers' relative imports.
- A background probe fetches 64KB from the CDN (and checks the origin
site) every 3 min and logs state flips to `server.log`; `GET
/api/cdn` (public) returns the latest throughput + ~1h history +
origin state. Use it to check if "playback broken" is the CDN again.
- The front-end is version-pinned: the server injects the git SHA as the
`?v=` cache-bust on `app.js`/`style.css` at startup (index.html itself
is `no-store`), so a restart is a cache bump — nothing to hand-bump.
@@ -147,6 +161,3 @@ SPEC.md design + the verified anidb.app data flow
and deterministic; the Playwright browser tests scrape and play LIVE
upstream content, so they are opt-in: `ANIWEB_LIVE_TESTS=1
.venv/bin/pytest tests/ -q` (needs playwright chromium installed).
- Episode titles are not available: anidb's episode API only returns
`{id, number, number2, filler}`. Adding titles needs a free
MyAnimeList API key.