219 lines
15 KiB
Markdown
219 lines
15 KiB
Markdown
# arr-tui — Specification
|
|
|
|
A terminal UI to search for movies and series, add them to Radarr / Sonarr, and watch the downloads progress in SABnzbd.
|
|
|
|
- **Language:** Go
|
|
- **TUI library:** [tview](https://github.com/rivo/tview) (on top of tcell)
|
|
- **License:** MIT
|
|
- **Module:** `git.stinnesbeck.com/nils/arr-tui`
|
|
- **Status:** M9 done
|
|
|
|
## 1. Goals
|
|
|
|
1. Search for movies (Radarr) and series (Sonarr) from one place.
|
|
2. Add a result to Radarr/Sonarr with sensible defaults (quality profile, root folder, monitored) and trigger a search so it gets downloaded.
|
|
3. Show the SABnzbd queue and history with live progress.
|
|
4. Be fast and keyboard-driven; usable over SSH.
|
|
|
|
### Non-goals (for now)
|
|
- Managing indexers, download clients, or Radarr/Sonarr settings.
|
|
- Replacing the Radarr/Sonarr web UIs for library management.
|
|
- Torrent clients (SABnzbd / Usenet only).
|
|
- Multi-user / auth beyond API keys.
|
|
|
|
## 2. How the pieces fit together
|
|
|
|
```
|
|
┌────────────┐ lookup / add / release search ┌──────────┐
|
|
│ │ ────────────────────────────────▶ │ Radarr │──┐
|
|
│ arr-tui │ └──────────┘ │ sends NZB
|
|
│ │ ────────────────────────────────▶ ┌──────────┐ │
|
|
│ │ │ Sonarr │──┤
|
|
└─────┬──────┘ └──────────┘ │
|
|
│ queue / history / pause / resume ▼
|
|
└────────────────────────────────────────▶ ┌──────────────┐
|
|
│ SABnzbd │
|
|
└──────────────┘
|
|
```
|
|
|
|
Radarr/Sonarr are expected to already have SABnzbd configured as their download client. arr-tui talks to all three APIs directly; it does **not** send NZBs to SABnzbd itself, except possibly via the manual-release flow, which still goes through Radarr/Sonarr (see 4.3).
|
|
|
|
## 3. External APIs
|
|
|
|
| Service | API | Auth | Endpoints used |
|
|
|---|---|---|---|
|
|
| Radarr | v3 REST (JSON) | `X-Api-Key` header | `GET /api/v3/movie/lookup?term=`, `GET /api/v3/movie`, `POST /api/v3/movie`, `GET /api/v3/qualityprofile`, `GET /api/v3/rootfolder`, `GET /api/v3/queue`, `GET /api/v3/release?movieId=`, `POST /api/v3/release`, `POST /api/v3/command` (`MoviesSearch`), `GET /api/v3/system/status` |
|
|
| Sonarr | v3 REST (JSON) | `X-Api-Key` header | `GET /api/v3/series/lookup?term=`, `GET /api/v3/series`, `POST /api/v3/series`, `GET /api/v3/qualityprofile`, `GET /api/v3/rootfolder`, `GET /api/v3/queue`, `GET /api/v3/release?episodeId=` / `?seriesId=&seasonNumber=`, `POST /api/v3/release`, `POST /api/v3/command` (`SeriesSearch`, `SeasonSearch`, `EpisodeSearch`), `GET /api/v3/system/status` |
|
|
| SABnzbd | `/api?mode=...&output=json` | `apikey` query param | `mode=queue`, `mode=history`, `mode=pause` / `resume`, `mode=queue&name=delete`, `mode=version` |
|
|
|
|
Notes:
|
|
- Endpoint shapes should be verified against each instance's own Swagger / the official docs when implementing; Radarr and Sonarr occasionally change fields between versions.
|
|
- Lookup results include an `id` of `0` when the item is not yet in the library; a non-zero `id` means it is already added. We use this to show an "in library" badge.
|
|
|
|
## 4. Features
|
|
|
|
### 4.1 MVP (v0.1)
|
|
|
|
- **Config:** load URLs + API keys from a config file; fail with a clear message if missing or unreachable.
|
|
- **Search:** a Movies view (Radarr) and a Series view (Sonarr), each with a search box. Results list with title, year, rating, status, and an "in library" marker. Detail pane with overview, runtime, genres, network/studio.
|
|
- **Add:** from a result, open a small form: quality profile, root folder, monitored (yes/no), "search now" (yes/no); for series also monitor mode (all / future / first season / none). Submit calls `POST /movie` or `POST /series` with `addOptions.searchForMovie` / `searchForMissingEpisodes`.
|
|
- **Interactive release search:** for a movie (or a series' season/episode) in the library, list releases from indexers (`GET /release`) with title, indexer, size, quality, age, and rejection reasons; sort/filter; grab a chosen one (`POST /release`). Rejected releases can be grabbed only after an explicit confirmation. The list can be filtered by quality, codec (x265/x264/AV1), HDR/DV/SDR, audio format and language; releases carry no media info, so codec and HDR are read from the title, quality and custom formats (a release that does not name its codec never matches a codec filter).
|
|
- **Downloads view:** all downloads and queues in one place, auto-refreshing every few seconds:
|
|
- SABnzbd queue (name, size, progress, ETA, speed) and history (completed / failed, with retry).
|
|
- Radarr and Sonarr queues (status, warnings, import errors), so stuck imports are visible.
|
|
- Global speed / pause state in the status bar. Pause/resume and delete a SABnzbd job.
|
|
- **Status bar / help:** the bottom bar lists the keys that work in the current view or panel; the top right shows the connection state of each service; `?` opens the full key reference.
|
|
- **Notifications and log (F5):** feedback about actions (added, deleted, grabbed, failures) appears as popups in the bottom right that fade away (errors stay longer). Everything is also kept in the Log view; only connection changes of the background polling are logged, not every failed poll.
|
|
|
|
### 4.2 v0.2 (done, M8)
|
|
|
|
- **Library view:** movies (top) and series (below) with the details of the selected item on the right; Tab switches between the two lists. Filters (all, missing, downloaded, monitored, unmonitored), a file-property filter (`F`: quality 2160p/1080p/720p/SD, codec x265/x264/AV1, HDR/DV/SDR, audio format Atmos/TrueHD/DTS:X/DTS-HD/DTS/DD+/DD/AAC/FLAC/Opus, audio language; changes apply live to the list behind the dialog, including while moving through an open dropdown, and Esc restores the previous filter) and a text filter apply to both lists. Movie properties come from the movie list; series properties are read per series from `GET /episodefile?seriesId=` only while a property filter is active, cached while a series' file count and size stay the same, and a series matches when any of its files does. `t` toggles monitoring, `s` starts an automatic search, `i`/Enter opens the release search (movies) or the drill-down (series).
|
|
- **Series drill-down:** seasons and episodes, toggle monitoring per season/episode, trigger season/episode search and release search.
|
|
|
|
### 4.3 Later / ideas
|
|
|
|
- Calendar / upcoming view.
|
|
- Desktop/terminal notification when a download completes.
|
|
- Multiple instances per service (e.g. 4K Radarr).
|
|
- Poster preview via sixel / kitty graphics where supported.
|
|
|
|
## 5. UI Design (tview)
|
|
|
|
Layout sketch (Library view; the other views follow the same frame):
|
|
|
|
```
|
|
┌ arr-tui ───────────────────────────────────────────────────────────┐
|
|
│ 1 Movies 2 Series 3 Downloads 4 Library 5 Log ● Radarr ● Sonarr ● SAB │ ← tab bar + connection state
|
|
│ Library · Movies 12/40 · Series 5/9 · filter: missing │
|
|
│ Filter: ______________________ │
|
|
├─ Movies ───────────────────────┬─ Details ─────────────────────────┤
|
|
│ ● Movie One (2021) missing │ Title (Year) │
|
|
│ ● Movie Two (2019) ✓ WEB-DL │ ★ 7.8 · 142 min · Sci-Fi │
|
|
├─ Series ───────────────────────┤ Overview text wraps here... │
|
|
│ ● Show One (2008) 12/62 │ │
|
|
│ ○ Show Two (2015) 10/10 │ │
|
|
├────────────────────────────────┴───────────────────────────────────┤
|
|
│ Tab movies/series · f filter · / text · t monitor · ? help · q quit │ ← key hints of the current view
|
|
└────────────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
Popups for action feedback appear in the bottom right and fade away; the Log view (F5) keeps the history.
|
|
|
|
tview primitives: `Pages` (views and stacked overlays), `Flex` (layout), `InputField` (search), `List`/`Table` (results, queues, library), `TextView` (details, log), `Form` (add dialog), `Modal` (confirmations). Popups are drawn by a wrapper around the root primitive.
|
|
|
|
Keybindings (the status bar shows the ones that apply to the current view):
|
|
|
|
| Key | Action |
|
|
|---|---|
|
|
| `F1-F5` | Switch view (Movies, Series, Downloads, Library, Log); also while typing, and from full-screen views. Dialogs block them. |
|
|
| `Tab` / `Shift+Tab` | Switch panel within the current view (never the view): search box / results, Downloads panels, Library lists |
|
|
| `?` / `F9` | Help (`F9` also works while typing) |
|
|
| `q` / `Ctrl+C` | Quit |
|
|
| `Esc` | Close a dialog or a full-screen view |
|
|
| `/` | Movies, Series, Library: focus the search / text filter |
|
|
| `Enter` / `a` | Movies, Series: add the selected result (dialog: ↑↓ move, ←→ change dropdowns) |
|
|
| `i` | Movies, Series, Library: interactive release search (series: seasons and episodes) |
|
|
| `F` | Library and release list: filter by properties (quality, codec, HDR/DV, audio, language) |
|
|
| `f` | Library: cycle the status filter; release list: hide rejected releases |
|
|
| `t` | Library, episodes: toggle monitored; Downloads history: retry a failed download |
|
|
| `s` | Library, episodes: automatic search; release list: cycle sort (best / size / age) |
|
|
| `Enter` / `g` | Release list: grab the selected release (rejected ones ask for confirmation) |
|
|
| `Enter` | Library: open; episodes: expand a season or search releases for an episode |
|
|
| `p` / `d` / `b` / `r` | Downloads: pause/resume SABnzbd, delete job / history entry / arr queue item, remove from arr queue + blocklist, refresh |
|
|
| `r` | Library, episodes: reload |
|
|
| `G` / `c` | Log: follow new entries / clear |
|
|
|
|
## 6. Architecture
|
|
|
|
- All network I/O runs in goroutines; UI updates go through `app.QueueUpdateDraw`. Never block the UI thread.
|
|
- Debounced search (e.g. 300 ms) with request cancellation via `context.Context`.
|
|
- A single ticker drives SABnzbd/queue polling, only while the Downloads view (or the status bar) needs it.
|
|
- HTTP client per service with timeouts, and a typed error that the UI can show in the status bar rather than crashing.
|
|
|
|
Proposed layout:
|
|
|
|
```
|
|
arr-tui/
|
|
├── cmd/arr-tui/main.go # entry point, wires config + UI
|
|
├── internal/
|
|
│ ├── config/ # load/validate config
|
|
│ ├── radarr/ # client + types
|
|
│ ├── sonarr/ # client + types
|
|
│ ├── sabnzbd/ # client + types
|
|
│ ├── arr/ # shared bits (HTTP helper, quality profile, root folder types)
|
|
│ └── ui/ # tview views: search, add form, downloads, status bar
|
|
├── SPECS.md
|
|
├── go.mod
|
|
└── README.md
|
|
```
|
|
|
|
Dependencies: `github.com/rivo/tview`, `github.com/gdamore/tcell/v2`. Prefer the standard library `net/http` and `encoding/json` for the API clients — no generated SDKs.
|
|
|
|
## 7. Configuration
|
|
|
|
Format: JSON. Location: `$XDG_CONFIG_HOME/arr-tui/config.json` (fallback `~/.config/arr-tui/config.json`), overridable with `--config`. Environment variables override file values (e.g. `ARR_TUI_RADARR_APIKEY`) so secrets don't have to live in the file. Parsed with the standard library `encoding/json` (unknown fields rejected to catch typos).
|
|
|
|
```json
|
|
{
|
|
"radarr": {
|
|
"url": "http://localhost:7878",
|
|
"apikey": "...",
|
|
"default_quality_profile": "HD-1080p",
|
|
"default_root_folder": "/movies"
|
|
},
|
|
"sonarr": {
|
|
"url": "http://localhost:8989",
|
|
"apikey": "...",
|
|
"default_quality_profile": "HD-1080p",
|
|
"default_root_folder": "/tv"
|
|
},
|
|
"sabnzbd": {
|
|
"url": "http://localhost:8080",
|
|
"apikey": "..."
|
|
},
|
|
"ui": {
|
|
"refresh_seconds": 3
|
|
}
|
|
}
|
|
```
|
|
|
|
The default quality profile / root folder fields are optional.
|
|
|
|
The file should be created with mode `0600`; warn if it is more permissive. API keys are never logged.
|
|
|
|
## 8. Testing
|
|
|
|
- Unit tests for each client using `httptest.Server` with recorded JSON fixtures.
|
|
- Config parsing / validation tests.
|
|
- UI logic (formatting, state) kept out of tview widgets so it can be tested without a terminal.
|
|
- Manual testing against real Radarr/Sonarr/SABnzbd instances (`dev/up.sh` starts pinned Radarr/Sonarr/SABnzbd containers, see `dev/README.md`).
|
|
|
|
## 9. Milestones
|
|
|
|
- [x] **M0** — Project skeleton: `go mod init`, config loading, empty tview app with tabs and status bar.
|
|
- [x] **M1** — Radarr client: system status, lookup, quality profiles, root folders, add movie.
|
|
- [x] **M2** — Movie search + add flow in the UI.
|
|
- [x] **M3** — Sonarr client and series search + add flow.
|
|
- [x] **M4** — SABnzbd client and Downloads view (queue + history) with live updates and pause/resume/delete.
|
|
- [x] **M5** — Radarr/Sonarr queues merged into the Downloads view.
|
|
- [x] **M6** — Interactive release search (movies, then series seasons/episodes) and grab.
|
|
- [x] **M7** — Polish: help screen (`?`/F9), per-service connection indicators, README, Makefile release builds, `--version`.
|
|
- [x] **M8** — Library view (movies/series, filters, text filter, monitor toggle, automatic search) and series drill-down (seasons/episodes, monitoring, search, release search).
|
|
- [x] **M9** — Popup notifications, Log view (F5), context-sensitive key hints in the status bar.
|
|
- [ ] **M10+** — ideas: calendar, completion notifications, delete from library, multiple instances.
|
|
|
|
## 10. Open Questions
|
|
|
|
Open: none.
|
|
|
|
|
|
Decided:
|
|
|
|
- Go module path: `git.stinnesbeck.com/nils/arr-tui`.
|
|
- One instance each of Radarr, Sonarr and SABnzbd (multi-instance is a later idea).
|
|
- Minimum Go version: latest stable.
|
|
- License: MIT (Nils Stinnesbeck). The tool will be published, so keep secrets out of the repo, include a `config.example.json`, and write a proper README.
|
|
- Config format: JSON.
|
|
- Interactive release search: in the MVP.
|
|
- Downloads view: shows SABnzbd and the Radarr/Sonarr queues from the start.
|