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
+31
View File
@@ -9,6 +9,7 @@ import (
"git.stinnesbeck.com/nils/arr-tui/internal/arr"
"git.stinnesbeck.com/nils/arr-tui/internal/radarr"
"git.stinnesbeck.com/nils/arr-tui/internal/sonarr"
"git.stinnesbeck.com/nils/arr-tui/internal/trakt"
)
// result is one search hit, independent of which *arr produced it.
@@ -47,6 +48,10 @@ type backend interface {
add(ctx context.Context, r result, c addChoice) (id int, err error)
// interactive starts an interactive release search for a library item.
interactive(v *searchView, r result)
// lists are the Trakt lists offered while the search box is empty, and
// resolve looks a list entry up (nil without error if it is not found).
lists() []trakt.List
resolve(ctx context.Context, e trakt.Entry) (result, error)
}
// --- Radarr ---
@@ -83,6 +88,19 @@ func (b radarrBackend) search(ctx context.Context, term string) ([]result, error
return out, err
}
func (radarrBackend) lists() []trakt.List { return trakt.MovieLists }
func (b radarrBackend) resolve(ctx context.Context, e trakt.Entry) (result, error) {
if e.IDs.TMDB == 0 {
return nil, errNotListed
}
found, err := b.search(ctx, radarr.TMDBTerm(e.IDs.TMDB))
if err != nil || len(found) == 0 {
return nil, err
}
return found[0], nil
}
func (b radarrBackend) add(ctx context.Context, r result, c addChoice) (int, error) {
created, err := b.c.AddMovie(ctx, r.(movieResult).Movie, radarr.AddOptions{
QualityProfileID: c.profileID,
@@ -144,6 +162,19 @@ func (b sonarrBackend) search(ctx context.Context, term string) ([]result, error
return out, err
}
func (sonarrBackend) lists() []trakt.List { return trakt.ShowLists }
func (b sonarrBackend) resolve(ctx context.Context, e trakt.Entry) (result, error) {
if e.IDs.TVDB == 0 {
return nil, errNotListed
}
found, err := b.search(ctx, "tvdb:"+itoa(e.IDs.TVDB))
if err != nil || len(found) == 0 {
return nil, err
}
return found[0], nil
}
func (b sonarrBackend) add(ctx context.Context, r result, c addChoice) (int, error) {
created, err := b.c.AddSeries(ctx, r.(seriesResult).Series, sonarr.AddOptions{
QualityProfileID: c.profileID,