Trending, most watched and anticipated movies and series from Trakt

With an empty search box, Movies and Series show one of Trakt's public
lists: trending now, most watched this week, anticipated, popular, and
for movies the US box office. [ ] switch lists, r reloads. Each entry is
looked up in Radarr (tmdb:) or Sonarr (tvdb:), so it behaves like a
search result: details, poster, tick when in the library, add, releases.

Needs a Trakt client ID: new optional trakt.client_id setting (Config
view section "Trakt", ARR_TUI_TRAKT_CLIENT_ID), masked like the API keys
and checked by Test. ARR_TUI_TRAKT_URL points at another server for
development.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-23 23:05:35 +02:00
co-authored by Claude Opus 5.5
parent b71659b357
commit 4dba794b26
18 changed files with 764 additions and 40 deletions
+23 -11
View File
@@ -17,6 +17,7 @@ import (
"git.stinnesbeck.com/nils/arr-tui/internal/radarr"
"git.stinnesbeck.com/nils/arr-tui/internal/sabnzbd"
"git.stinnesbeck.com/nils/arr-tui/internal/sonarr"
"git.stinnesbeck.com/nils/arr-tui/internal/trakt"
)
type view struct {
@@ -31,14 +32,17 @@ type view struct {
// App is the top-level arr-tui application.
type App struct {
app *tview.Application
theme theme // the colors in use
themed *themedScreen // draws with them; nil until Run
tabs *tview.TextView
pages *tview.Pages
status *tview.TextView
views []view
active int
app *tview.Application
theme theme // the colors in use
trakt *trakt.Client // trending, most watched and anticipated lists
// The Movies and Series views, to refresh their Trakt lists.
searches []*searchView
themed *themedScreen // draws with them; nil until Run
tabs *tview.TextView
pages *tview.Pages
status *tview.TextView
views []view
active int
toasts []toast
logs *logView
@@ -170,9 +174,16 @@ func New(cfg *config.Config) *App {
},
time.Duration(cfg.UI.RefreshSeconds)*time.Second)
a.downloads = downloads
a.trakt = trakt.NewClient(cfg.Trakt.ClientID, "arr-tui")
if u := os.Getenv("ARR_TUI_TRAKT_URL"); u != "" {
a.trakt.SetBaseURL(u) // another Trakt API, for development (dev/screenshots)
}
movies := newSearchView(a, radarrBackend{radarrClient})
series := newSearchView(a, sonarrBackend{sonarrClient})
a.searches = []*searchView{movies, series}
a.views = []view{
{key: '1', name: "Movies", body: newSearchView(a, radarrBackend{radarrClient}).root, hints: searchHints},
{key: '2', name: "Series", body: newSearchView(a, sonarrBackend{sonarrClient}).root, hints: searchHints},
{key: '1', name: "Movies", body: movies.root, onShow: movies.activate, hints: searchHints},
{key: '2', name: "Series", body: series.root, onShow: series.activate, hints: searchHints},
{key: '3', name: "Library", body: library.root, onShow: library.activate, hints: libraryHints},
{key: '4', name: "Downloads", body: downloads.root, onShow: downloads.activate, onHide: downloads.deactivate, hints: downloads.hints},
{key: '5', name: "Calendar", body: calendar.root, onShow: calendar.activate, hints: calendarHints},
@@ -205,6 +216,7 @@ func New(cfg *config.Config) *App {
syncFocus = func(p tview.Primitive) { a.app.SetFocus(p) }
a.app.SetInputCapture(a.handleKey)
a.switchTo(0)
movies.activate() // switchTo does not call onShow for the view already active
if !cfg.AnyConfigured() {
// First start (or nothing set up): go where that is done.
a.switchTo(a.viewIndex("Config"))
@@ -656,7 +668,7 @@ func (a *App) renderTabs() {
}
func searchHints() string {
return "/ search · Tab list/search · Enter add · i releases"
return "/ search · [ ] lists · Tab list/search · Enter add · i releases"
}
func libraryHints() string {