220 lines
16 KiB
Markdown
220 lines
16 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.
|
|
- **Posters:** the detail panes show a poster thumbnail above the text, rendered as half-block characters with 24-bit colors (each cell is two pixels; JPEG/PNG only, decoded and downscaled in the process, so no terminal graphics protocol is needed). Library items fetch `/MediaCover/<id>/poster-250.jpg` from their Radarr/Sonarr instance with the API key; search results fall back to the provider's `remoteUrl` (fetched without credentials). Posters are cached in memory, failed downloads are not retried for two minutes, and the poster shrinks or disappears when the pane is too short. `ui.covers: false` disables it.
|
|
- **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 (status: 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; each dropdown only lists options that, together with the other chosen criteria, still match at least one item (series files are read when the dialog opens so this is accurate)) act only on the focused list, and each list keeps its own status filter, property filter and text; the lists show their active filters in their titles. 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 Library 4 Downloads 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, Library, Downloads, 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.
|