363 lines
9.8 KiB
Markdown
363 lines
9.8 KiB
Markdown
# ani-cli web
|
|
|
|
Small local web UI for a system-wide `ani-cli` install.
|
|
|
|
Current version: `0.37.1`
|
|
|
|
## Project Layout
|
|
|
|
```text
|
|
ani-cli-web/
|
|
app.py Main entrypoint and runtime wiring.
|
|
app_support.py Shared config, state, and helper utilities.
|
|
Dockerfile Container image build.
|
|
docker-compose.yaml Example Compose service.
|
|
queue_jobs.py Download queue and watchlist refresh workers.
|
|
watchlist_identity.py Download-to-watchlist identity matching.
|
|
title_matching.py Shared title normalization and matching.
|
|
http_handler.py HTTP routing and access control.
|
|
web_templates.py Shared page aggregation.
|
|
search_page.py Search page template.
|
|
queue_page.py Queue page template.
|
|
config_page.py Config page template.
|
|
watchlist_page.py Watchlist page template.
|
|
docker-entrypoint.sh Container startup wrapper.
|
|
ani-cli-web Launcher script.
|
|
test_app.py Regression tests.
|
|
VERSION Project version.
|
|
CHANGELOG.md Change history.
|
|
README.md Project documentation.
|
|
```
|
|
|
|
## Quick Start
|
|
|
|
Run the app:
|
|
|
|
```sh
|
|
./ani-cli-web
|
|
```
|
|
|
|
Run with debug logging:
|
|
|
|
```sh
|
|
./ani-cli-web --debug
|
|
```
|
|
|
|
Open:
|
|
|
|
```text
|
|
http://127.0.0.1:8421/
|
|
```
|
|
|
|
Default bind address is `127.0.0.1:8421`.
|
|
|
|
## Docker
|
|
|
|
The image installs `ani-cli` system-wide, downloads the latest upstream `yt-dlp` release binary from GitHub, and clones `ani-cli-web` into `/app` during build.
|
|
|
|
Default build args:
|
|
|
|
```text
|
|
ANI_CLI=https://github.com/pystardust/ani-cli.git
|
|
ANI_CLI_BRANCH=master
|
|
ANI_CLI_WEB=https://gitea.coreplay.eu/Dymas/ani-cli-web.git
|
|
```
|
|
|
|
Build:
|
|
|
|
```sh
|
|
docker build -t ani-cli-web .
|
|
```
|
|
|
|
Build with a custom `ani-cli` branch:
|
|
|
|
```sh
|
|
docker build \
|
|
--build-arg ANI_CLI_BRANCH=my-branch \
|
|
--build-arg ANI_CLI=https://github.com/pystardust/ani-cli.git \
|
|
--build-arg ANI_CLI_WEB=https://gitea.coreplay.eu/Dymas/ani-cli-web.git \
|
|
-t ani-cli-web .
|
|
```
|
|
|
|
Run:
|
|
|
|
```sh
|
|
docker run --rm \
|
|
-p 8421:8421 \
|
|
-e UPDATE_ON_START=true \
|
|
-e USER_UID="$(id -u)" \
|
|
-e USER_GID="$(id -g)" \
|
|
-v "$(pwd)/downloads:/downloads" \
|
|
-v "$(pwd)/.ani-cli-web:/app/.ani-cli-web" \
|
|
ani-cli-web
|
|
```
|
|
|
|
Compose:
|
|
|
|
```sh
|
|
docker compose up --build -d
|
|
```
|
|
|
|
Use host ownership for mounted downloads:
|
|
|
|
```sh
|
|
USER_UID="$(id -u)" USER_GID="$(id -g)" docker compose up --build -d
|
|
```
|
|
|
|
Container notes:
|
|
|
|
- `ani-cli` is installed at `/usr/local/bin/ani-cli`.
|
|
- `yt-dlp` is installed at `/usr/local/bin/yt-dlp` from the latest GitHub release during build.
|
|
- `ani-cli-web` runs from `/app`.
|
|
- Downloads go to `/downloads`.
|
|
- App state lives in `/app/.ani-cli-web`.
|
|
- The container binds `0.0.0.0:8421`.
|
|
- `UPDATE_ON_START=true` runs `sudo ani-cli --update` before startup.
|
|
- `USER_UID` and `USER_GID` keep mounted files from being owned by `root`.
|
|
|
|
## What It Does
|
|
|
|
- Search AllAnime in `sub` or `dub`.
|
|
- Queue downloads with editable folder, quality, season, and episode range.
|
|
- Save defaults on `/config`.
|
|
- Track shows on `/watchlist`.
|
|
- Refresh watchlist episode counts in the background.
|
|
- Schedule periodic watchlist refresh jobs.
|
|
- Send Discord webhook notifications for refresh discoveries and failures.
|
|
- Send Discord webhook notifications for completed downloads.
|
|
- Cache watchlist thumbnails locally.
|
|
- Sync successful downloads back into the watchlist.
|
|
- Keep queue and watchlist data in SQLite under `.ani-cli-web/`.
|
|
|
|
## Pages
|
|
|
|
### Search Page
|
|
|
|
Path: `/`
|
|
|
|
Use it to:
|
|
|
|
1. Search for an anime.
|
|
2. Load episodes.
|
|
3. Adjust folder, quality, season, and episode range.
|
|
4. Add the show to the queue or watchlist.
|
|
|
|
Highlights:
|
|
|
|
- Search stays focused on finding shows, loading episodes, and queueing new downloads.
|
|
- Added-to-queue notices now point to the dedicated Queue page for progress tracking.
|
|
- Search request guards still avoid stale responses overwriting newer results.
|
|
- The shared sidebar menu now shows one navigation entry per line for cleaner balance across all pages.
|
|
|
|
### Queue Page
|
|
|
|
Path: `/queue`
|
|
|
|
Use it to:
|
|
|
|
1. Monitor active, pending, failed, and finished download jobs.
|
|
2. Inspect recent per-job logs with newest lines first.
|
|
3. Retry failed jobs.
|
|
4. Remove finished jobs.
|
|
5. Cancel running or pending jobs.
|
|
|
|
Highlights:
|
|
|
|
- Automatic queue polling every 2.5 seconds.
|
|
- Dedicated queue pagination and cleanup controls.
|
|
- Search and queue workflows are now separated into distinct pages.
|
|
- Each queue job card can be collapsed, and finished jobs start collapsed by default to keep the page compact.
|
|
|
|
### Config Page
|
|
|
|
Path: `/config`
|
|
|
|
Use it to set:
|
|
|
|
1. Default download folder.
|
|
2. Default `sub` or `dub` mode.
|
|
3. Default quality.
|
|
4. Scheduled watchlist refresh enable or disable.
|
|
5. Scheduled refresh period in minutes.
|
|
6. Delay between shows during `Refresh All`.
|
|
7. Discord webhook URL.
|
|
8. Discord notification events plus a test action.
|
|
|
|
Saved settings are written to `.ani-cli-web/config.json`.
|
|
|
|
Discord notification events:
|
|
|
|
- Download completed.
|
|
- Refresh finished and found new episodes.
|
|
- Refresh failed or finished with per-show refresh errors.
|
|
- Refresh interrupted by restart or shutdown.
|
|
- Unexpected runtime errors.
|
|
|
|
### Watchlist
|
|
|
|
Path: `/watchlist`
|
|
|
|
Categories:
|
|
|
|
- `Watching`
|
|
- `Planned`
|
|
- `Finished`
|
|
- `Dropped`
|
|
|
|
Ways to add shows:
|
|
|
|
1. From the Search page.
|
|
2. Manually on `/watchlist`.
|
|
|
|
Main behavior:
|
|
|
|
1. `Refresh` updates one show.
|
|
2. `Refresh All` runs in the background.
|
|
3. `Download all` prompts for `sub` or `dub` and queues every currently available episode directly.
|
|
4. Clicking the title opens the source page.
|
|
5. `Remove` deletes the entry and cached poster.
|
|
6. Each card has an inline category selector.
|
|
7. Summary cards show total tracked shows plus `sub` and `dub` counts.
|
|
8. Pagination shows 30 cards per page.
|
|
|
|
Status behavior:
|
|
|
|
- `Sub` and `Dub` pills turn green when that track looks complete.
|
|
- Cards with both `sub` and `dub` fully ready get a thin green border.
|
|
- Finished cards show a small check badge.
|
|
- Green check means downloaded through the app.
|
|
- Gray check means finished manually.
|
|
|
|
Download sync:
|
|
|
|
- Existing watchlist entries move to `Finished` when a download succeeds.
|
|
- Missing entries are added to `Finished`.
|
|
- If ani-cli resolves a different `show_id`, the app can reconcile a unique title match instead of creating a duplicate finished row.
|
|
|
|
Thumbnail behavior:
|
|
|
|
- Thumbnails are served from the local `/api/watchlist/thumb/...` route.
|
|
- Cached thumbnails are reused.
|
|
|
|
Title behavior:
|
|
|
|
- Watchlist refreshes also try to pull alternative English and synonym titles from the AllManga show page.
|
|
- Watchlist cards show an `Alternative titles` button when names are available.
|
|
- Pressing that button expands the stored alternative titles on demand.
|
|
- Missing covers are warmed in small batches.
|
|
- Failed lookups back off before retrying.
|
|
- You can force `Reload` or use `Upload` when no thumbnail exists.
|
|
|
|
Refresh behavior:
|
|
|
|
- Per-show refreshes are queued in the background.
|
|
- Queued refresh state survives restarts.
|
|
- Duplicate refresh queueing is avoided.
|
|
- Scheduled refresh runs log start, stop, success, and failure to stdout.
|
|
- `Refresh All` works one show at a time with a configurable delay.
|
|
- When a Discord webhook is configured, new-episode refresh results include one notification card per anime with sub and dub counts and a cached thumbnail attachment when available.
|
|
|
|
## Access Guard
|
|
|
|
By default the app only serves loopback clients such as `127.0.0.1` and `::1`.
|
|
|
|
To allow LAN or remote access:
|
|
|
|
```sh
|
|
ANI_CLI_WEB_ALLOW_REMOTE=1
|
|
ANI_CLI_WEB_REMOTE_TOKEN=choose-a-long-random-token
|
|
```
|
|
|
|
Non-local clients must send one of:
|
|
|
|
- `Authorization: Bearer <token>`
|
|
- `X-AniCli-Web-Token: <token>`
|
|
|
|
Browser bootstrap also works:
|
|
|
|
```text
|
|
http://YOUR-HOST:8421/?token=your-token-here
|
|
```
|
|
|
|
For normal browser visits, the app now shows a sign-in page automatically when the token cookie is missing.
|
|
|
|
The app stores the token in an HTTP-only cookie and redirects to the clean URL. The cookie is persistent, so you usually only need to sign in once per browser.
|
|
|
|
## Download Queue
|
|
|
|
Path: `/queue`
|
|
|
|
The queue is stored in SQLite, shown 10 jobs at a time, and managed from its dedicated page.
|
|
|
|
Available actions:
|
|
|
|
- `Retry`
|
|
- `Cancel`
|
|
- `Remove`
|
|
- `Retry failed`
|
|
- `Remove finished`
|
|
|
|
Downloads are staged first, then moved into a media-friendly layout:
|
|
|
|
```text
|
|
ANI_CLI_DOWNLOAD_DIR/anime_name/Season 01/anime_name - S01E01.mp4
|
|
```
|
|
|
|
## Runtime State
|
|
|
|
All local state is stored under:
|
|
|
|
```text
|
|
ani-cli-web/.ani-cli-web/
|
|
```
|
|
|
|
Main contents:
|
|
|
|
- `config.json`
|
|
- `state.sqlite3`
|
|
- `thumbnails/`
|
|
- AniDB title cache data
|
|
- staging files for active downloads
|
|
|
|
Legacy state from older locations is migrated on startup.
|
|
|
|
## Configuration
|
|
|
|
Environment variables:
|
|
|
|
- `ANI_CLI_BIN`: path or command name for `ani-cli`
|
|
- `ANI_CLI_DOWNLOAD_DIR`: initial default download folder
|
|
- `ANI_CLI_MODE`: initial mode, `sub` or `dub`
|
|
- `ANI_CLI_QUALITY`: initial quality such as `best`, `1080`, or `720`
|
|
- `ANI_CLI_WEB_HOST`: server host, default `127.0.0.1`
|
|
- `ANI_CLI_WEB_PORT`: server port, default `8421`
|
|
- `ANI_CLI_WEB_ALLOW_REMOTE`: allow non-loopback clients
|
|
- `ANI_CLI_WEB_REMOTE_TOKEN`: shared secret for remote access
|
|
- `ANI_CLI_WEB_DEBUG`: enable debug logging
|
|
- `ANI_CLI_WEB_STATE_ROOT`: override the project-local state directory
|
|
- `UPDATE_ON_START`: run `sudo ani-cli --update` in the container before startup
|
|
- `USER_UID` and `USER_GID`: run the container as a specific host user/group
|
|
|
|
Saved config fields include:
|
|
|
|
- `discord_webhook_url`
|
|
- `discord_webhook_events`
|
|
- `watchlist_auto_refresh_enabled`
|
|
- `watchlist_auto_refresh_minutes`
|
|
- `watchlist_refresh_delay_seconds`
|
|
|
|
## Testing
|
|
|
|
Run the regression suite with:
|
|
|
|
```sh
|
|
python3 -m unittest test_app.py
|
|
```
|
|
|
|
## Notes
|
|
|
|
- Runtime boot is lazy. Importing `app.py` does not immediately start worker threads.
|
|
- Queue and watchlist data share the same SQLite database: `.ani-cli-web/state.sqlite3`.
|
|
- Runtime folders such as `.ani-cli-web/` and `downloads/` are ignored by git.
|
|
- The Docker image clones both upstream repositories during build.
|