Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 39 additions & 10 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ env:

jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }}
permissions:
contents: read
steps:
Expand All @@ -21,6 +24,9 @@ jobs:
bun-version: latest

- name: Build frontend
# bash on both legs: the default pwsh on windows-latest only propagates the exit code
# of the LAST command, so a failing `bun install` would not fail the step.
shell: bash
run: |
cd src/internal/frontend
bun install --frozen-lockfile
Expand All @@ -29,12 +35,17 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.24'
go-version: '1.25'
cache: true

- name: Build project
run: go build -v ./...

- name: Assert CGO-free build (single static binary)
env:
CGO_ENABLED: 0
run: go build -v ./...

test-backend-unit:
runs-on: ubuntu-latest
permissions:
Expand All @@ -45,7 +56,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.24'
go-version: '1.25'
cache: true

- name: Create fake frontend dist
Expand All @@ -56,6 +67,24 @@ jobs:
- name: Run unit tests
run: go test -v ./src/tests/unit/... ./src/internal/...

# Confirms the Windows cross-device (ERROR_NOT_SAME_DEVICE) errno mapping and hardlink
# behavior, which can only be verified by actually running on Windows.
test-backend-windows:
runs-on: windows-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.25'
cache: true

- name: Run torrent + files package tests
run: go test -v ./src/internal/files/... ./src/internal/torrents/...

test-backend-integration:
runs-on: ubuntu-latest
permissions:
Expand Down Expand Up @@ -85,16 +114,10 @@ jobs:
--tag test-mock-nyaa --load \
src/tests/mocks/nyaa

docker buildx build \
--cache-from type=gha,scope=integration-mock-qbittorrent \
--cache-to type=gha,mode=max,scope=integration-mock-qbittorrent \
--tag test-mock-qbittorrent --load \
src/tests/mocks/qbittorrent

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.24'
go-version: '1.25'
cache: true

- name: Start services
Expand All @@ -105,7 +128,13 @@ jobs:

- name: Run integration tests
env:
# DAEMON_URL is also the opt-in gate: without it the suite skips, so that a
# plain `go test ./...` never reconfigures a daemon it wasn't pointed at.
DAEMON_URL: http://localhost:8091
# This path is resolved inside the daemon *container*, not on the runner.
# Keep it under /app/data (mounted volume) — the config endpoint hardlink-probes
# it and rejects filesystems that don't support hardlinks.
TEST_COMPLETED_PATH: /app/data/aad-test/library
run: go test -v ./src/tests/integration/...

- name: Cleanup
Expand Down
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ node_modules/
# Runtime data (Docker volumes)
runtime/
!runtime/.gitkeep
!runtime/qbittorrent-config/qBittorrent/qBittorrent.conf

# Environment variables
.env
Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Guidance for Claude Code (claude.ai/code) in this repo.

## Project Overview

AutoAnimeDownloader = Go daemon. Auto-downloads anime episodes. Integrates Anilist (watch list), Nyaa (torrent scraper), qBittorrent. Has embedded Svelte web UI + REST API.
AutoAnimeDownloader = Go daemon. Auto-downloads anime episodes. Integrates Anilist (watch list), Nyaa (torrent scraper), and an embedded BitTorrent client (github.com/cenkalti/rain/v2 — no external qBittorrent). Has embedded Svelte web UI + REST API.

## Important — After Any Change

Expand Down
37 changes: 17 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

**Automatically downloads your anime from your Anilist watching list.**

Syncs with [Anilist](https://anilist.co), scrapes [Nyaa](https://nyaa.si) for matching torrents, and sends them to [qBittorrent](https://www.qbittorrent.org) — all unattended. Includes an embedded Svelte web UI for monitoring and configuration.
Syncs with [Anilist](https://anilist.co), scrapes [Nyaa](https://nyaa.si) for matching torrents, and downloads them with a built-in BitTorrent client — all unattended, from a single self-contained binary (no external torrent client to install or configure). Includes an embedded Svelte web UI for monitoring and configuration.

[![Build Status](https://github.com/icarosuper/AutoAnimeDownloader/workflows/Build/badge.svg)](https://github.com/icarosuper/AutoAnimeDownloader/actions)

Expand All @@ -26,10 +26,12 @@ Syncs with [Anilist](https://anilist.co), scrapes [Nyaa](https://nyaa.si) for ma
## Features

- **Automatic Downloads** — monitors your Anilist watching list and downloads new episodes as they air
- **Embedded BitTorrent client** — downloads and seeds internally (via [rain](https://github.com/cenkalti/rain)); no qBittorrent or other external client required
- **Jellyfin-ready library** — completed episodes are hardlinked into your library folder (optionally with Jellyfin naming) while the original keeps seeding
- **Web UI** — modern browser interface for monitoring, configuration, and control
- **CLI** — command-line interface for scripting and advanced users
- **Real-time Updates** — WebSocket support for live status in the UI
- **Self-contained** — frontend embedded in the binary, no separate server needed
- **Self-contained** — frontend and torrent client embedded in the binary, no separate server needed
- **Cross-platform** — Linux (amd64/arm64) and Windows

## Screenshots
Expand All @@ -40,17 +42,14 @@ Syncs with [Anilist](https://anilist.co), scrapes [Nyaa](https://nyaa.si) for ma

## Requirements

- **qBittorrent** 4.3.0+ with WebUI enabled
- **Anilist account** (username only, no password needed)
- A **completed anime path** (the download/seeding directory is derived from it automatically, so it's the only path you configure)

### Setting up qBittorrent WebUI
That's it — the BitTorrent client is built in, so there is no qBittorrent (or any other torrent client) to install or configure.

1. Open qBittorrent → **Tools → Options → Web UI**
2. Enable **Web User Interface (Remote control)**
3. Set a username and password (or leave blank for local use)
4. Note the port (default: 8080)
> **Upgrading from a qBittorrent-based version?** See the [Migration Guide](docs/guides/migration-embedded-torrent.md) — `completed_anime_path` is now required. There is no separate `save_path` field anymore: the download directory lives nested inside `completed_anime_path` (same volume by construction), and any pre-existing torrent data at a legacy `save_path` is migrated into the new location automatically on first boot after upgrading.

![qBittorrent WebUI tutorial](https://i.imgur.com/vYgUdyy.png)
> **Ports:** the embedded client listens on a default port range (20000–30000) and finds peers via DHT/PEX. There is no automatic port forwarding (UPnP/NAT-PMP); forwarding a port is optional and only improves inbound connectivity.

## Installation

Expand Down Expand Up @@ -106,7 +105,7 @@ Open **http://localhost:8091** in your browser.

- **Status** — daemon status, start/stop control, force episode check
- **Episodes** — browse downloaded episodes
- **Config** — Anilist username, qBittorrent URL, paths, intervals
- **Config** — Anilist username, paths, intervals

### CLI

Expand All @@ -124,7 +123,7 @@ See the [CLI Guide](docs/guides/cli.md) for the full reference.

## Building from Source

**Prerequisites:** Go 1.24+, Node.js 20+, npm, make (Linux)
**Prerequisites:** Go 1.25+, Node.js 20+, npm, make (Linux)

The frontend must be built before the Go binaries, as it's embedded into the daemon.

Expand Down Expand Up @@ -152,22 +151,20 @@ Key settings:
| Setting | Description |
|---|---|
| Anilist Username | Your Anilist username |
| qBittorrent URL | Usually `http://localhost:8080` |
| Save Path | Where to save in-progress downloads |
| Completed Anime Path | Where to move finished series |
| Save Path | Download/seeding working directory |
| Completed Anime Path | Jellyfin library — required, and must be on the same volume as the Save Path (episodes are hardlinked into it) |
| Check Interval | How often to check for new episodes (minutes) |

## Troubleshooting

**Daemon won't start**
- Check if port 8091 is in use: `ss -tlnp | grep 8091`
- Verify qBittorrent WebUI is enabled
- Make sure both the save path and completed anime path are set and on the same volume
- Check service logs: `systemctl --user status autoanimedownloader`

**Can't connect to qBittorrent**
- Verify WebUI is enabled in qBittorrent settings
- Confirm the URL and port in AAD configuration
- Ensure qBittorrent is running before starting AAD
**Downloaded but nothing shows up in the library**
- The save path and completed anime path must be on the **same filesystem/volume** — completed episodes are hardlinked, which cannot cross volumes
- Check the logs for `Organize:` messages: `autoanimedownloader logs --search Organize`

**Anime not found on Nyaa**
- The anime title from Anilist may not match Nyaa's naming — set a custom search title in the anime config
Expand All @@ -194,6 +191,6 @@ Licensed under the [GNU General Public License v3.0](https://www.gnu.org/license

- Backend: [Go](https://golang.org/)
- Frontend: [Svelte](https://svelte.dev/)
- Downloads: [qBittorrent](https://www.qbittorrent.org/)
- BitTorrent client: [rain](https://github.com/cenkalti/rain)
- Watch list: [Anilist](https://anilist.co/)
- Torrents: [Nyaa](https://nyaa.si/)
2 changes: 1 addition & 1 deletion docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ COPY src/internal/frontend/ ./
RUN bun run build

# Stage 2: Build Go daemon (frontend is embedded)
FROM golang:1.24-alpine AS go-builder
FROM golang:1.25-alpine AS go-builder

WORKDIR /build

Expand Down
2 changes: 1 addition & 1 deletion docker/Dockerfile.build.amd64
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# This can be used for consistent builds or when local dependencies are missing
# Frontend is expected to be already built in src/internal/frontend/dist

FROM golang:1.24
FROM golang:1.25

# Install dependencies
RUN apt-get update && apt-get install -y \
Expand Down
2 changes: 1 addition & 1 deletion docker/Dockerfile.build.arm64
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# This uses a native ARM64 container to compile with CGO enabled
# Frontend is expected to be already built in src/internal/frontend/dist

FROM --platform=linux/arm64 golang:1.24
FROM --platform=linux/arm64 golang:1.25

# Install dependencies
RUN apt-get update && apt-get install -y \
Expand Down
2 changes: 1 addition & 1 deletion docker/Dockerfile.build.windows
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# Uses mingw-w64 for cross-compilation from Linux
# Frontend is expected to be already built in src/internal/frontend/dist

FROM golang:1.24
FROM golang:1.25

# Install mingw-w64 for Windows cross-compilation with CGO
RUN apt-get update && apt-get install -y \
Expand Down
2 changes: 1 addition & 1 deletion docker/Dockerfile.test
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM golang:1.24-alpine AS go-builder
FROM golang:1.25-alpine AS go-builder

WORKDIR /build

Expand Down
23 changes: 5 additions & 18 deletions docker/docker-compose.test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,21 +32,6 @@ services:
networks:
- test-network

mock-qbittorrent:
image: test-mock-qbittorrent
build:
context: ../src/tests/mocks/qbittorrent
dockerfile: Dockerfile
dns:
- 8.8.8.8
- 1.1.1.1
expose:
- "8082"
environment:
- PORT=8082
networks:
- test-network

daemon:
image: test-daemon
build:
Expand All @@ -64,11 +49,9 @@ services:
- PORT=:8091
- ANILIST_API_URL=http://mock-anilist:8080
- NYAA_URL=http://mock-nyaa:8081
- QBITTORRENT_URL=http://mock-qbittorrent:8082
depends_on:
- mock-anilist
- mock-nyaa
- mock-qbittorrent
networks:
- test-network
healthcheck:
Expand All @@ -79,14 +62,18 @@ services:
start_period: 10s

test:
image: golang:1.24-alpine
image: golang:1.25-alpine
volumes:
- ..:/workspace
- go-mod-cache:/go/pkg/mod
working_dir: /workspace
environment:
- CGO_ENABLED=0
- DAEMON_URL=http://daemon:8091
# Paths the daemon container writes to. Both under /app/data (the mounted volume) so
# they share a filesystem — the config endpoint rejects cross-volume paths because
# completed episodes are hardlinked into the library.
- TEST_COMPLETED_PATH=/app/data/aad-test/library
command: >
sh -c "
apk add --no-cache wget &&
Expand Down
4 changes: 0 additions & 4 deletions docker/entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,8 @@ mkdir -p "$CONFIG_DIR"

# Valores padrão
ANILIST_USERNAME="${ANILIST_USERNAME:-}"
SAVE_PATH="${SAVE_PATH:-/app/downloads}"
COMPLETED_ANIME_PATH="${COMPLETED_ANIME_PATH:-/app/downloads/completed}"
CHECK_INTERVAL="${CHECK_INTERVAL:-10}"
QBITTORRENT_URL="${QBITTORRENT_URL:-http://qbittorrent:8080}"
MAX_EPISODES_PER_ANIME="${MAX_EPISODES_PER_ANIME:-12}"
EPISODE_RETRY_LIMIT="${EPISODE_RETRY_LIMIT:-5}"
DELETE_WATCHED_EPISODES="${DELETE_WATCHED_EPISODES:-true}"
Expand All @@ -29,11 +27,9 @@ fi
if [ ! -f "$CONFIG_FILE" ] || [ -n "$FORCE_CONFIG_UPDATE" ]; then
cat > "$CONFIG_FILE" <<EOF
{
"save_path": "$SAVE_PATH",
"completed_anime_path": "$COMPLETED_ANIME_PATH",
"anilist_username": "$ANILIST_USERNAME",
"check_interval": $CHECK_INTERVAL,
"qbittorrent_url": "$QBITTORRENT_URL",
"max_episodes_per_anime": $MAX_EPISODES_PER_ANIME,
"episode_retry_limit": $EPISODE_RETRY_LIMIT,
"delete_watched_episodes": $DELETE_WATCHED_EPISODES_JSON,
Expand Down
13 changes: 4 additions & 9 deletions docs/TODO.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
> Versão atual: v1.3.3
> Cada task indica o bump que deve gerar na versão do app (semver).

- Download de animes — **+0.1.0**
- Animes completos
- Ajustar download em batch
- Contar episódios
- Renomear arquivos de batch — **+0.1.0**
- Nova tela de prioridades dos torrents — **+0.1.0**
- Nessa tela vai aparecer a ordem de prioridades da pesquisa no nyaa
- Usuário por reordenar, adicionar e excluir itens
- Botão pra voltar pro default
- Substituir torrent de um anime ou episódio **+0.1.0**
- Hoje vc tem que trazer um torrent de fora e inserir no campo
- Quero um botão que abre a listagem do nyaa dentro do app
- Baixar animes avulsos — **+0.1.0**
- Nova tela de baixar animes avulsos.
- Nessa tela vai ter uma barra de pesquisa.
Expand All @@ -20,12 +21,6 @@
- Abstrair interface TorrentSource com múltiplas implementações
- Integração Jellyfin/Plex — **+0.1.0**
- Trigger de library scan após download concluído
- Dashboard de estatísticas — **+0.1.0**
- Episódios baixados por semana/anime
- Fansubs mais usados
- Dados já disponíveis no downloaded_episodes JSONL
- Windows — **+0.1.0**
- Instalar ao invés de rodar separado
- Dar detach e rodar no background
- Torrent imbutido — **+1.0.0**
- Adicionar modal com opções de torrents de animes / episódios — **+0.1.0**
Loading
Loading