From 1f0dbcb453231ecea87a2149405526f092ea12b4 Mon Sep 17 00:00:00 2001 From: soup Date: Wed, 6 Nov 2024 12:41:38 +0100 Subject: [PATCH] feat(arrs): add support for including unmonitored titles (#96) * feat(arrs): add support for including unmonitored titles * fix: revert go import ordering * fix: remove includeUnmonitored from unsupported in configTemplate --- README.md | 50 +++++++++++++++++------------- internal/domain/config.go | 4 +++ internal/processor/radarr.go | 12 +++---- internal/processor/service.go | 11 +++++-- internal/processor/service_test.go | 17 ++++++++++ internal/processor/sonarr.go | 27 +++++++--------- 6 files changed, 74 insertions(+), 47 deletions(-) create mode 100644 internal/processor/service_test.go diff --git a/README.md b/README.md index a955d85..2ebf3cc 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ Omegabrr transforms items monitored by arrs or lists into autobrr filters. Useful for automating your filters for monitored media or racing criteria. ## Table of Contents + - [Config](#config) - [Tags](#tags) - [Lists](#lists) @@ -36,6 +37,7 @@ clients: filters: - 15 # Change me #matchRelease: false / true + #includeUnmonitored: false # Set to true to include unmonitored items - name: sonarr type: sonarr @@ -50,6 +52,7 @@ clients: - 14 # Change me #matchRelease: false / true #excludeAlternateTitles: false/ true # only works for Sonarr and defaults to false + #includeUnmonitored: false # Set to true to include unmonitored items - name: lidarr type: lidarr @@ -104,7 +107,7 @@ lists: url: https://api.autobrr.com/lists/trakt/popular-tv filters: - 25 # Change me - + - name: StevenLu type: trakt url: https://api.autobrr.com/lists/stevenlu @@ -133,27 +136,27 @@ lists: If you're trying to reach radarr or sonarr hosted on swizzin from some other location, you need to do it like this with basic auth: ```yaml - arr: - - name: radarr - type: radarr - host: https://domain.com/radarr - apikey: YOUR_API_KEY - basicAuth: - user: username - pass: password - filters: - - 15 # Change me +arr: + - name: radarr + type: radarr + host: https://domain.com/radarr + apikey: YOUR_API_KEY + basicAuth: + user: username + pass: password + filters: + - 15 # Change me ``` Same goes for autobrr if it's behind basic auth. ```yaml - autobrr: - host: http://localhost:7474 - apikey: YOUR_API_KEY - basicAuth: - user: username - pass: password +autobrr: + host: http://localhost:7474 + apikey: YOUR_API_KEY + basicAuth: + user: username + pass: password ``` ### Tags @@ -188,7 +191,7 @@ If you want to exclude certain tags, you can use the `tagsExclude`. ### Lists -Formerly known as regbrr and maintained by community members is now integrated into omegabrr! We now maintain the lists of media. +Formerly known as regbrr and maintained by community members is now integrated into omegabrr! We now maintain the lists of media. **Trakt** @@ -215,6 +218,10 @@ Readarr will only use the `Match releases` field for now, so setting `matchRelea You can drop alternate show titles from being added by setting `excludeAlternateTitles: true` for Sonarr in your config. +### Include Unmonitored Items + +By default, omegabrr only processes monitored items. You can include unmonitored items by setting `includeUnmonitored: true` in your arr configuration. This is particularly useful in cross-seed scenarios where you want to match against all items. + ## Plaintext lists specific options Plaintext lists can be anything, therefore you can optionally set `matchRelease: true` or `album: true` to use these fields in your autobrr filter. If not set, it will use the `Movies / Shows` field. @@ -242,6 +249,7 @@ server: port: 7441 apiToken: MY_NEW_LONG_SECURE_TOKEN ``` + Call with `omegabrr generate-token` If you are using docker `docker exec omegabrr omegabrr generate-token` Optionally call with `--length `for a custom length. @@ -276,7 +284,7 @@ The API Token can be set as either an HTTP header like `X-API-Token`, or be pass ### Docker compose -Check the `docker-compose.yml` example. +Check the `docker-compose.yml` example. 1. Set `user: 1000:1000` with your user id you can get with the `id` command, or remove to run as **root**. 2. Set the `volume` so it matches your system. To run from the same path as the `docker-compose` first create a config dir like `mkdir config`, and place this `./config:/config` in the compose file. This will create a default config on the first run. @@ -287,13 +295,13 @@ If you have custom networks then make sure to add those, so it can communicate w For users who prioritize container security, we offer alternative Docker images built on [Distroless](https://github.com/GoogleContainerTools/distroless). Specifically the `distroless/static-debian12:nonroot` base image. -Distroless images do not contain a package manager or shell, thereby reducing the potential attack surface and making them a more secure option. These stripped-back images contain only the application and its runtime dependencies. +Distroless images do not contain a package manager or shell, thereby reducing the potential attack surface and making them a more secure option. These stripped-back images contain only the application and its runtime dependencies. ### Systemd On Linux-based systems it is recommended to run omegabrr as a systemd service. -Download the [latest binary](https://github.com/autobrr/omegabrr/releases/latest) for your system and place it in `/usr/local/bin`. +Download the [latest binary](https://github.com/autobrr/omegabrr/releases/latest) for your system and place it in `/usr/local/bin`. Example: Download binary diff --git a/internal/domain/config.go b/internal/domain/config.go index ff1a62a..48c5f5f 100644 --- a/internal/domain/config.go +++ b/internal/domain/config.go @@ -52,6 +52,7 @@ type ArrConfig struct { TagsExclude []string `koanf:"tagsExclude"` MatchRelease bool `koanf:"matchRelease"` ExcludeAlternateTitles bool `koanf:"excludeAlternateTitles"` + IncludeUnmonitored bool `koanf:"includeUnmonitored"` } type ArrType string @@ -234,6 +235,7 @@ clients: # apikey: API_KEY # filters: # - 15 # Change me + # includeUnmonitored: false # Set to true to include unmonitored items #- name: radarr4k # type: radarr @@ -241,6 +243,7 @@ clients: # apikey: API_KEY # filters: # - 16 # Change me + # includeUnmonitored: false # Set to true to include unmonitored items #- name: sonarr # type: sonarr @@ -248,6 +251,7 @@ clients: # apikey: API_KEY # filters: # - 14 # Change me + # includeUnmonitored: false # Set to true to include unmonitored items # #excludeAlternateTitles: true # defaults to false #- name: readarr diff --git a/internal/processor/radarr.go b/internal/processor/radarr.go index 6f0e32b..2014e42 100644 --- a/internal/processor/radarr.go +++ b/internal/processor/radarr.go @@ -8,8 +8,8 @@ import ( "github.com/autobrr/omegabrr/internal/domain" "github.com/autobrr/omegabrr/pkg/autobrr" - "github.com/pkg/errors" + "github.com/pkg/errors" "github.com/rs/zerolog" "github.com/rs/zerolog/log" "golift.io/starr" @@ -91,13 +91,12 @@ func (s Service) processRadarr(ctx context.Context, cfg *domain.ArrConfig, logge logger.Debug().Msgf("found %d movies to process", len(movies)) titleSet := make(map[string]struct{}) - var monitoredTitles int + var processedTitles int for _, movie := range movies { m := movie - // only want monitored - if !m.Monitored { + if !s.shouldProcessItem(m.Monitored, cfg) { continue } @@ -116,8 +115,7 @@ func (s Service) processRadarr(ctx context.Context, cfg *domain.ArrConfig, logge } } - // increment monitored titles - monitoredTitles++ + processedTitles++ // Taking the international title and the original title and appending them to the titles array. for _, title := range []string{m.Title, m.OriginalTitle} { @@ -135,7 +133,7 @@ func (s Service) processRadarr(ctx context.Context, cfg *domain.ArrConfig, logge } sort.Strings(uniqueTitles) - logger.Debug().Msgf("from a total of %d movies we found %d monitored and created %d release titles", len(movies), monitoredTitles, len(uniqueTitles)) + logger.Debug().Msgf("from a total of %d movies we found %d titles and created %d release titles", len(movies), processedTitles, len(uniqueTitles)) return uniqueTitles, nil } diff --git a/internal/processor/service.go b/internal/processor/service.go index 77987ed..c36d249 100644 --- a/internal/processor/service.go +++ b/internal/processor/service.go @@ -47,6 +47,14 @@ func (s Service) newAutobrrClient() *autobrr.Client { return a } +// shouldProcessItem determines if an item should be processed based on its monitored status and configuration +func (s Service) shouldProcessItem(monitored bool, arrConfig *domain.ArrConfig) bool { + if arrConfig.IncludeUnmonitored { + return true + } + return monitored +} + func (s Service) ProcessArrs(ctx context.Context, dryRun bool) []string { var processingErrors []string @@ -91,7 +99,6 @@ func (s Service) ProcessArrs(ctx context.Context, dryRun bool) []string { } return processingErrors - } func (s Service) ProcessLists(ctx context.Context, dryRun bool) []string { @@ -133,13 +140,11 @@ func (s Service) ProcessLists(ctx context.Context, dryRun bool) []string { log.Error().Err(err).Str("type", "steam").Str("client", listsClient.Name).Msg("error while processing Steam wishlist, continuing with other lists") processingErrors = append(processingErrors, fmt.Sprintf("Steam - %s: %v", listsClient.Name, err)) } - } } } return processingErrors - } func (s Service) GetFilters(ctx context.Context) ([]autobrr.Filter, error) { diff --git a/internal/processor/service_test.go b/internal/processor/service_test.go new file mode 100644 index 0000000..131b3be --- /dev/null +++ b/internal/processor/service_test.go @@ -0,0 +1,17 @@ +package processor + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/autobrr/omegabrr/internal/domain" +) + +func TestService_shouldProcessItem(t *testing.T) { + s := &Service{} + cfg := &domain.ArrConfig{ + IncludeUnmonitored: true, + } + assert.True(t, s.shouldProcessItem(false, cfg), "unmonitored items should be processed when includeUnmonitored is true") +} diff --git a/internal/processor/sonarr.go b/internal/processor/sonarr.go index f3b6f2f..ea565fd 100644 --- a/internal/processor/sonarr.go +++ b/internal/processor/sonarr.go @@ -8,8 +8,8 @@ import ( "github.com/autobrr/omegabrr/internal/domain" "github.com/autobrr/omegabrr/pkg/autobrr" - "github.com/pkg/errors" + "github.com/pkg/errors" "github.com/rs/zerolog" "github.com/rs/zerolog/log" "golift.io/starr" @@ -98,44 +98,39 @@ func (s Service) processSonarr(ctx context.Context, cfg *domain.ArrConfig, logge logger.Debug().Msgf("found %d shows to process", len(shows)) titleSet := make(map[string]struct{}) - var monitoredTitles int + var processedTitles int for _, show := range shows { - s := show + series := show - // only want monitored - if !s.Monitored { + if !s.shouldProcessItem(series.Monitored, cfg) { continue } if len(cfg.TagsInclude) > 0 { - if len(s.Tags) == 0 { + if len(series.Tags) == 0 { continue } - if !containsTag(tags, s.Tags, cfg.TagsInclude) { + if !containsTag(tags, series.Tags, cfg.TagsInclude) { continue } } if len(cfg.TagsExclude) > 0 { - if containsTag(tags, s.Tags, cfg.TagsExclude) { + if containsTag(tags, series.Tags, cfg.TagsExclude) { continue } } - // increment monitored titles - monitoredTitles++ - - //titles = append(titles, rls.MustNormalize(s.Title)) - //titles = append(titles, rls.MustClean(s.Title)) + processedTitles++ - titles := processTitle(s.Title, cfg.MatchRelease) + titles := processTitle(series.Title, cfg.MatchRelease) for _, title := range titles { titleSet[title] = struct{}{} } if !cfg.ExcludeAlternateTitles { - for _, title := range s.AlternateTitles { + for _, title := range series.AlternateTitles { altTitles := processTitle(title.Title, cfg.MatchRelease) for _, altTitle := range altTitles { titleSet[altTitle] = struct{}{} @@ -150,7 +145,7 @@ func (s Service) processSonarr(ctx context.Context, cfg *domain.ArrConfig, logge } sort.Strings(uniqueTitles) - logger.Debug().Msgf("from a total of %d shows we found %d monitored and created %d release titles", len(shows), monitoredTitles, len(uniqueTitles)) + logger.Debug().Msgf("from a total of %d shows we found %d titles and created %d release titles", len(shows), processedTitles, len(uniqueTitles)) return uniqueTitles, nil }