- CDN probe no longer downloads 64KB of video (or even a variant playlist): one small master-playlist fetch proves reachability. State stays OK/DEGRADED, kbps is no longer measured, and the pill/ panel only show a rate when one exists. - hianime: 5x getSourcesNew re-roll -> 1 attempt. The enc payload is deterministic per data-id (observed identical across calls), so re-rolling only hammered the provider. - anikoto: 2x re-roll -> 1 attempt per server, same reason. Kept: idle-gated + 30-min degraded backoff from the previous commit, the single _get retry (protects real user playback from CF blips), and the origin page check (the 'down' signal). Net upstream traffic is now what an actual viewer would generate, nothing more.
ani-web
Web front-end for anime streaming: search, browse, and stream anime in 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.
Quick start
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.
Accounts are CLI-created only (no self-signup):
.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
Passwords are PBKDF2-SHA256 in server/data/aniweb.db (SQLite, 0600);
sessions are signed 30-day HttpOnly SameSite=Lax cookies, Secure
via run.sh, so remote plain-HTTP can't hold a session — the HTTPS
proxy is the way in. See SPEC.md for the rest.
Features
-
History — server-side and per-user: a new device picks up your exact watch state after login. Entries show poster, progress, and a per-entry delete.
-
Home — continue-watching hero (poster, position, Resume) plus a recently-played grid with progress bars.
-
Search — poster-grid results from the active source; your last 8 searches (per user) in a live-filtering dropdown. Results open the detail view (synopsis, season tabs, filler badges).
-
Player — resume at the saved position, skip intro (MAL timings), dub/sub toggle, playback speed, hide fillers, auto-next with a 5-second countdown, ✓ on completed episodes. Playback goes through a backend HLS proxy (the CDN sends no CORS headers); segments are pulled across parallel byte-range lanes so a slow connection doesn't stall.
-
Status — the header pill shows one consolidated state — up / slow (KB/s) / down — as a single dot; tap opens a panel with the per-host breakdown, the ~1h CDN trend (dashed slow-threshold line), and origin up/down history. A failure overlay names the CDN when it's the likely culprit.
-
Stats —
?stats: watch minutes, episodes, streaks, sub/dub split, weekday/per-day heatmaps, top-10 shows, top searches (30–365 day windows).
Layout
server/ FastAPI app (app.py), source providers (providers/: hianime,
anidb, anikoto) over shared HTTP + dispatch (upstream.py)
static/ front-end: index.html, login.html, app.js, hls.min.js (vendored)
tests/ pytest suite: hermetic default, live browser e2e opt-in
docs/img/ README screenshots
run.sh launch/restart the server (sets ANIWEB_SECURE=1)
SPEC.md design, verified anidb.app data flow, security + runbook notes
Dev
.venv/bin/pytest tests/ -q— hermetic suite against a throwaway server (never touches real data). Live-upstream browser e2e is opt-in:ANIWEB_LIVE_TESTS=1 .venv/bin/pytest tests/ -q(needs playwright chromium).ANI_SOURCE=anidb .venv/bin/python -m server.upstream— live self-check of the anidb fetchers (must be the-mform; plain script execution breaks the providers' relative imports).- Everything else (Cloudflare/impersonation, HLS proxy, CDN probe, auth hardening, cache-busting): SPEC.md.




