diff --git a/frontend/src/Settings/General/HostSettings.js b/frontend/src/Settings/General/HostSettings.js
index 99255b0d5..1514f81fc 100644
--- a/frontend/src/Settings/General/HostSettings.js
+++ b/frontend/src/Settings/General/HostSettings.js
@@ -22,6 +22,7 @@ function HostSettings(props) {
urlBase,
instanceName,
applicationUrl,
+ indexerNameTemplate,
enableSsl,
sslPort,
sslCertPath,
@@ -105,6 +106,25 @@ function HostSettings(props) {
/>
+
+ {translate('IndexerNameTemplate')}
+
+
+
+
+ {
+ private Mock _configService;
+
+ [SetUp]
+ public void Setup()
+ {
+ _configService = Mocker.GetMock();
+ }
+
+ [TestCase(null)]
+ [TestCase("")]
+ [TestCase(" ")]
+ public void FormatIndexerName_should_return_empty_when_invalid_input(string indexerName)
+ {
+ _configService.Setup(s => s.IndexerNameTemplate).Returns("{name} ({instance})");
+
+ var result = Subject.FormatIndexerName(indexerName, "Prowlarr");
+
+ result.Should().Be("");
+ }
+
+ [Test]
+ public void FormatIndexerName_should_format_with_template()
+ {
+ _configService.Setup(s => s.IndexerNameTemplate).Returns("{name} ({instance})");
+
+ var result = Subject.FormatIndexerName("MyIndexer", "MyProwlarr");
+
+ result.Should().Be("MyIndexer (MyProwlarr)");
+ }
+
+ [TestCase(null)]
+ [TestCase("")]
+ [TestCase(" ")]
+ public void FormatIndexerName_should_use_fallback_instance_name(string instanceName)
+ {
+ _configService.Setup(s => s.IndexerNameTemplate).Returns("{name} ({instance})");
+
+ var result = Subject.FormatIndexerName("MyIndexer", instanceName);
+
+ result.Should().Be($"MyIndexer ({DefaultInstanceName})");
+ }
+
+ [TestCase(null)]
+ [TestCase("")]
+ [TestCase(" ")]
+ public void FormatIndexerName_should_return_original_name_when_no_template(string template)
+ {
+ _configService.Setup(s => s.IndexerNameTemplate).Returns(template);
+
+ var result = Subject.FormatIndexerName("MyIndexer", "MyProwlarr");
+
+ result.Should().Be("MyIndexer");
+ }
+
+ [Test]
+ public void FormatIndexerName_should_handle_custom_template()
+ {
+ _configService.Setup(s => s.IndexerNameTemplate).Returns("[{instance}] {name}");
+
+ var result = Subject.FormatIndexerName("MyIndexer", "MyProwlarr");
+
+ result.Should().Be("[MyProwlarr] MyIndexer");
+ }
+ }
+}
diff --git a/src/NzbDrone.Core/Applications/IndexerNameTemplateService.cs b/src/NzbDrone.Core/Applications/IndexerNameTemplateService.cs
new file mode 100644
index 000000000..204bb8f79
--- /dev/null
+++ b/src/NzbDrone.Core/Applications/IndexerNameTemplateService.cs
@@ -0,0 +1,46 @@
+using NzbDrone.Core.Configuration;
+
+namespace NzbDrone.Core.Applications
+{
+ public static class IndexerNameTemplateDefaults
+ {
+ public const string DefaultTemplate = "{name} ({instance})";
+ public const string DefaultInstanceName = "Prowlarr";
+ }
+
+ public interface IIndexerNameTemplateService
+ {
+ string FormatIndexerName(string indexerName, string instanceName);
+ }
+
+ public class IndexerNameTemplateService : IIndexerNameTemplateService
+ {
+ private readonly IConfigService _configService;
+
+ public IndexerNameTemplateService(IConfigService configService)
+ {
+ _configService = configService;
+ }
+
+ public string FormatIndexerName(string indexerName, string instanceName)
+ {
+ if (string.IsNullOrWhiteSpace(indexerName))
+ {
+ return string.Empty;
+ }
+
+ var template = _configService.IndexerNameTemplate;
+
+ if (string.IsNullOrWhiteSpace(template))
+ {
+ return indexerName;
+ }
+
+ var finalInstanceName = !string.IsNullOrWhiteSpace(instanceName) ? instanceName : IndexerNameTemplateDefaults.DefaultInstanceName;
+
+ return template
+ .Replace("{name}", indexerName)
+ .Replace("{instance}", finalInstanceName);
+ }
+ }
+}
diff --git a/src/NzbDrone.Core/Applications/LazyLibrarian/LazyLibrarian.cs b/src/NzbDrone.Core/Applications/LazyLibrarian/LazyLibrarian.cs
index 108972d6e..5838937b3 100644
--- a/src/NzbDrone.Core/Applications/LazyLibrarian/LazyLibrarian.cs
+++ b/src/NzbDrone.Core/Applications/LazyLibrarian/LazyLibrarian.cs
@@ -15,12 +15,14 @@ public class LazyLibrarian : ApplicationBase
private readonly ILazyLibrarianV1Proxy _lazyLibrarianV1Proxy;
private readonly IConfigFileProvider _configFileProvider;
+ private readonly IIndexerNameTemplateService _indexerNameTemplateService;
- public LazyLibrarian(ILazyLibrarianV1Proxy lazyLibrarianV1Proxy, IConfigFileProvider configFileProvider, IAppIndexerMapService appIndexerMapService, IIndexerFactory indexerFactory, Logger logger)
+ public LazyLibrarian(ILazyLibrarianV1Proxy lazyLibrarianV1Proxy, IConfigFileProvider configFileProvider, IAppIndexerMapService appIndexerMapService, IIndexerFactory indexerFactory, IIndexerNameTemplateService indexerNameTemplateService, Logger logger)
: base(appIndexerMapService, indexerFactory, logger)
{
_lazyLibrarianV1Proxy = lazyLibrarianV1Proxy;
_configFileProvider = configFileProvider;
+ _indexerNameTemplateService = indexerNameTemplateService;
}
public override ValidationResult Test()
@@ -155,8 +157,8 @@ private LazyLibrarianIndexer BuildLazyLibrarianIndexer(IndexerDefinition indexer
var lazyLibrarianIndexer = new LazyLibrarianIndexer
{
- Name = originalName ?? $"{indexer.Name} (Prowlarr)",
- Altername = $"{indexer.Name} (Prowlarr)",
+ Name = originalName ?? _indexerNameTemplateService.FormatIndexerName(indexer.Name, _configFileProvider.InstanceName),
+ Altername = originalName ?? _indexerNameTemplateService.FormatIndexerName(indexer.Name, _configFileProvider.InstanceName),
Host = $"{Settings.ProwlarrUrl.TrimEnd('/')}/{indexer.Id}/api",
Apikey = _configFileProvider.ApiKey,
Categories = string.Join(",", indexerCapabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray())),
diff --git a/src/NzbDrone.Core/Applications/Lidarr/Lidarr.cs b/src/NzbDrone.Core/Applications/Lidarr/Lidarr.cs
index d305b48d2..d6b96d652 100644
--- a/src/NzbDrone.Core/Applications/Lidarr/Lidarr.cs
+++ b/src/NzbDrone.Core/Applications/Lidarr/Lidarr.cs
@@ -21,13 +21,15 @@ public class Lidarr : ApplicationBase
private readonly ILidarrV1Proxy _lidarrV1Proxy;
private readonly ICached> _schemaCache;
private readonly IConfigFileProvider _configFileProvider;
+ private readonly IIndexerNameTemplateService _indexerNameTemplateService;
- public Lidarr(ICacheManager cacheManager, ILidarrV1Proxy lidarrV1Proxy, IConfigFileProvider configFileProvider, IAppIndexerMapService appIndexerMapService, IIndexerFactory indexerFactory, Logger logger)
+ public Lidarr(ICacheManager cacheManager, ILidarrV1Proxy lidarrV1Proxy, IConfigFileProvider configFileProvider, IAppIndexerMapService appIndexerMapService, IIndexerFactory indexerFactory, IIndexerNameTemplateService indexerNameTemplateService, Logger logger)
: base(appIndexerMapService, indexerFactory, logger)
{
_schemaCache = cacheManager.GetCache>(GetType());
_lidarrV1Proxy = lidarrV1Proxy;
_configFileProvider = configFileProvider;
+ _indexerNameTemplateService = indexerNameTemplateService;
}
public override ValidationResult Test()
@@ -247,7 +249,7 @@ private LidarrIndexer BuildLidarrIndexer(IndexerDefinition indexer, IndexerCapab
var lidarrIndexer = new LidarrIndexer
{
Id = id,
- Name = $"{indexer.Name} (Prowlarr)",
+ Name = _indexerNameTemplateService.FormatIndexerName(indexer.Name, _configFileProvider.InstanceName),
EnableRss = indexer.Enable && indexer.AppProfile.Value.EnableRss,
EnableAutomaticSearch = indexer.Enable && indexer.AppProfile.Value.EnableAutomaticSearch,
EnableInteractiveSearch = indexer.Enable && indexer.AppProfile.Value.EnableInteractiveSearch,
diff --git a/src/NzbDrone.Core/Applications/Mylar/Mylar.cs b/src/NzbDrone.Core/Applications/Mylar/Mylar.cs
index e9fd9ffe7..5674d56de 100644
--- a/src/NzbDrone.Core/Applications/Mylar/Mylar.cs
+++ b/src/NzbDrone.Core/Applications/Mylar/Mylar.cs
@@ -15,12 +15,14 @@ public class Mylar : ApplicationBase
private readonly IMylarV3Proxy _mylarV3Proxy;
private readonly IConfigFileProvider _configFileProvider;
+ private readonly IIndexerNameTemplateService _indexerNameTemplateService;
- public Mylar(IMylarV3Proxy mylarV3Proxy, IConfigFileProvider configFileProvider, IAppIndexerMapService appIndexerMapService, IIndexerFactory indexerFactory, Logger logger)
+ public Mylar(IMylarV3Proxy mylarV3Proxy, IConfigFileProvider configFileProvider, IAppIndexerMapService appIndexerMapService, IIndexerFactory indexerFactory, IIndexerNameTemplateService indexerNameTemplateService, Logger logger)
: base(appIndexerMapService, indexerFactory, logger)
{
_mylarV3Proxy = mylarV3Proxy;
_configFileProvider = configFileProvider;
+ _indexerNameTemplateService = indexerNameTemplateService;
}
public override ValidationResult Test()
@@ -155,8 +157,8 @@ private MylarIndexer BuildMylarIndexer(IndexerDefinition indexer, IndexerCapabil
var mylarIndexer = new MylarIndexer
{
- Name = originalName ?? $"{indexer.Name} (Prowlarr)",
- Altername = $"{indexer.Name} (Prowlarr)",
+ Name = originalName ?? _indexerNameTemplateService.FormatIndexerName(indexer.Name, _configFileProvider.InstanceName),
+ Altername = originalName ?? _indexerNameTemplateService.FormatIndexerName(indexer.Name, _configFileProvider.InstanceName),
Host = $"{Settings.ProwlarrUrl.TrimEnd('/')}/{indexer.Id}/api",
Apikey = _configFileProvider.ApiKey,
Categories = string.Join(",", indexerCapabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray())),
diff --git a/src/NzbDrone.Core/Applications/Radarr/Radarr.cs b/src/NzbDrone.Core/Applications/Radarr/Radarr.cs
index 6266f4ffa..90a85cf9a 100644
--- a/src/NzbDrone.Core/Applications/Radarr/Radarr.cs
+++ b/src/NzbDrone.Core/Applications/Radarr/Radarr.cs
@@ -21,13 +21,15 @@ public class Radarr : ApplicationBase
private readonly IRadarrV3Proxy _radarrV3Proxy;
private readonly ICached> _schemaCache;
private readonly IConfigFileProvider _configFileProvider;
+ private readonly IIndexerNameTemplateService _indexerNameTemplateService;
- public Radarr(ICacheManager cacheManager, IRadarrV3Proxy radarrV3Proxy, IConfigFileProvider configFileProvider, IAppIndexerMapService appIndexerMapService, IIndexerFactory indexerFactory, Logger logger)
+ public Radarr(ICacheManager cacheManager, IRadarrV3Proxy radarrV3Proxy, IConfigFileProvider configFileProvider, IAppIndexerMapService appIndexerMapService, IIndexerFactory indexerFactory, IIndexerNameTemplateService indexerNameTemplateService, Logger logger)
: base(appIndexerMapService, indexerFactory, logger)
{
_schemaCache = cacheManager.GetCache>(GetType());
_radarrV3Proxy = radarrV3Proxy;
_configFileProvider = configFileProvider;
+ _indexerNameTemplateService = indexerNameTemplateService;
}
public override ValidationResult Test()
@@ -245,7 +247,7 @@ private RadarrIndexer BuildRadarrIndexer(IndexerDefinition indexer, IndexerCapab
var radarrIndexer = new RadarrIndexer
{
Id = id,
- Name = $"{indexer.Name} (Prowlarr)",
+ Name = _indexerNameTemplateService.FormatIndexerName(indexer.Name, _configFileProvider.InstanceName),
EnableRss = indexer.Enable && indexer.AppProfile.Value.EnableRss,
EnableAutomaticSearch = indexer.Enable && indexer.AppProfile.Value.EnableAutomaticSearch,
EnableInteractiveSearch = indexer.Enable && indexer.AppProfile.Value.EnableInteractiveSearch,
diff --git a/src/NzbDrone.Core/Applications/Readarr/Readarr.cs b/src/NzbDrone.Core/Applications/Readarr/Readarr.cs
index efcc1eca0..480efbaf3 100644
--- a/src/NzbDrone.Core/Applications/Readarr/Readarr.cs
+++ b/src/NzbDrone.Core/Applications/Readarr/Readarr.cs
@@ -21,13 +21,15 @@ public class Readarr : ApplicationBase
private readonly ICached> _schemaCache;
private readonly IReadarrV1Proxy _readarrV1Proxy;
private readonly IConfigFileProvider _configFileProvider;
+ private readonly IIndexerNameTemplateService _indexerNameTemplateService;
- public Readarr(ICacheManager cacheManager, IReadarrV1Proxy readarrV1Proxy, IConfigFileProvider configFileProvider, IAppIndexerMapService appIndexerMapService, IIndexerFactory indexerFactory, Logger logger)
+ public Readarr(ICacheManager cacheManager, IReadarrV1Proxy readarrV1Proxy, IConfigFileProvider configFileProvider, IAppIndexerMapService appIndexerMapService, IIndexerFactory indexerFactory, IIndexerNameTemplateService indexerNameTemplateService, Logger logger)
: base(appIndexerMapService, indexerFactory, logger)
{
_schemaCache = cacheManager.GetCache>(GetType());
_readarrV1Proxy = readarrV1Proxy;
_configFileProvider = configFileProvider;
+ _indexerNameTemplateService = indexerNameTemplateService;
}
public override ValidationResult Test()
@@ -241,7 +243,7 @@ private ReadarrIndexer BuildReadarrIndexer(IndexerDefinition indexer, IndexerCap
var readarrIndexer = new ReadarrIndexer
{
Id = id,
- Name = $"{indexer.Name} (Prowlarr)",
+ Name = _indexerNameTemplateService.FormatIndexerName(indexer.Name, _configFileProvider.InstanceName),
EnableRss = indexer.Enable && indexer.AppProfile.Value.EnableRss,
EnableAutomaticSearch = indexer.Enable && indexer.AppProfile.Value.EnableAutomaticSearch,
EnableInteractiveSearch = indexer.Enable && indexer.AppProfile.Value.EnableInteractiveSearch,
diff --git a/src/NzbDrone.Core/Applications/Sonarr/Sonarr.cs b/src/NzbDrone.Core/Applications/Sonarr/Sonarr.cs
index bf58b85c5..2460055e7 100644
--- a/src/NzbDrone.Core/Applications/Sonarr/Sonarr.cs
+++ b/src/NzbDrone.Core/Applications/Sonarr/Sonarr.cs
@@ -21,13 +21,15 @@ public class Sonarr : ApplicationBase
private readonly ICached> _schemaCache;
private readonly ISonarrV3Proxy _sonarrV3Proxy;
private readonly IConfigFileProvider _configFileProvider;
+ private readonly IIndexerNameTemplateService _indexerNameTemplateService;
- public Sonarr(ICacheManager cacheManager, ISonarrV3Proxy sonarrV3Proxy, IConfigFileProvider configFileProvider, IAppIndexerMapService appIndexerMapService, IIndexerFactory indexerFactory, Logger logger)
+ public Sonarr(ICacheManager cacheManager, ISonarrV3Proxy sonarrV3Proxy, IConfigFileProvider configFileProvider, IAppIndexerMapService appIndexerMapService, IIndexerFactory indexerFactory, IIndexerNameTemplateService indexerNameTemplateService, Logger logger)
: base(appIndexerMapService, indexerFactory, logger)
{
_schemaCache = cacheManager.GetCache>(GetType());
_sonarrV3Proxy = sonarrV3Proxy;
_configFileProvider = configFileProvider;
+ _indexerNameTemplateService = indexerNameTemplateService;
}
public override ValidationResult Test()
@@ -253,7 +255,7 @@ private SonarrIndexer BuildSonarrIndexer(IndexerDefinition indexer, IndexerCapab
var sonarrIndexer = new SonarrIndexer
{
Id = id,
- Name = $"{indexer.Name} (Prowlarr)",
+ Name = _indexerNameTemplateService.FormatIndexerName(indexer.Name, _configFileProvider.InstanceName),
EnableRss = indexer.Enable && indexer.AppProfile.Value.EnableRss,
EnableAutomaticSearch = indexer.Enable && indexer.AppProfile.Value.EnableAutomaticSearch,
EnableInteractiveSearch = indexer.Enable && indexer.AppProfile.Value.EnableInteractiveSearch,
diff --git a/src/NzbDrone.Core/Applications/Whisparr/Whisparr.cs b/src/NzbDrone.Core/Applications/Whisparr/Whisparr.cs
index 0c149fc7c..1b47beac6 100644
--- a/src/NzbDrone.Core/Applications/Whisparr/Whisparr.cs
+++ b/src/NzbDrone.Core/Applications/Whisparr/Whisparr.cs
@@ -21,13 +21,15 @@ public class Whisparr : ApplicationBase
private readonly IWhisparrV3Proxy _whisparrV3Proxy;
private readonly ICached> _schemaCache;
private readonly IConfigFileProvider _configFileProvider;
+ private readonly IIndexerNameTemplateService _indexerNameTemplateService;
- public Whisparr(ICacheManager cacheManager, IWhisparrV3Proxy whisparrV3Proxy, IConfigFileProvider configFileProvider, IAppIndexerMapService appIndexerMapService, IIndexerFactory indexerFactory, Logger logger)
+ public Whisparr(ICacheManager cacheManager, IWhisparrV3Proxy whisparrV3Proxy, IConfigFileProvider configFileProvider, IAppIndexerMapService appIndexerMapService, IIndexerFactory indexerFactory, IIndexerNameTemplateService indexerNameTemplateService, Logger logger)
: base(appIndexerMapService, indexerFactory, logger)
{
_schemaCache = cacheManager.GetCache>(GetType());
_whisparrV3Proxy = whisparrV3Proxy;
_configFileProvider = configFileProvider;
+ _indexerNameTemplateService = indexerNameTemplateService;
}
public override ValidationResult Test()
@@ -232,7 +234,7 @@ private WhisparrIndexer BuildWhisparrIndexer(IndexerDefinition indexer, IndexerC
var whisparrIndexer = new WhisparrIndexer
{
Id = id,
- Name = $"{indexer.Name} (Prowlarr)",
+ Name = _indexerNameTemplateService.FormatIndexerName(indexer.Name, _configFileProvider.InstanceName),
EnableRss = indexer.Enable && indexer.AppProfile.Value.EnableRss,
EnableAutomaticSearch = indexer.Enable && indexer.AppProfile.Value.EnableAutomaticSearch,
EnableInteractiveSearch = indexer.Enable && indexer.AppProfile.Value.EnableInteractiveSearch,
diff --git a/src/NzbDrone.Core/Configuration/ConfigService.cs b/src/NzbDrone.Core/Configuration/ConfigService.cs
index 27a953823..bc90d3640 100644
--- a/src/NzbDrone.Core/Configuration/ConfigService.cs
+++ b/src/NzbDrone.Core/Configuration/ConfigService.cs
@@ -88,6 +88,12 @@ public bool LogIndexerResponse
set { SetValue("LogIndexerResponse", value); }
}
+ public string IndexerNameTemplate
+ {
+ get { return GetValue("indexernametemplate", Applications.IndexerNameTemplateDefaults.DefaultTemplate); }
+ set { SetValue("indexernametemplate", value); }
+ }
+
public int FirstDayOfWeek
{
get { return GetValueInt("FirstDayOfWeek", (int)CultureInfo.CurrentCulture.DateTimeFormat.FirstDayOfWeek); }
diff --git a/src/NzbDrone.Core/Configuration/IConfigService.cs b/src/NzbDrone.Core/Configuration/IConfigService.cs
index 5fa2ed005..c986c6944 100644
--- a/src/NzbDrone.Core/Configuration/IConfigService.cs
+++ b/src/NzbDrone.Core/Configuration/IConfigService.cs
@@ -53,6 +53,7 @@ public interface IConfigService
// Indexers
bool LogIndexerResponse { get; set; }
+ string IndexerNameTemplate { get; set; }
CertificateValidationType CertificateValidation { get; }
string ApplicationUrl { get; }
diff --git a/src/NzbDrone.Core/Localization/Core/en.json b/src/NzbDrone.Core/Localization/Core/en.json
index a789c6046..70d1905a6 100644
--- a/src/NzbDrone.Core/Localization/Core/en.json
+++ b/src/NzbDrone.Core/Localization/Core/en.json
@@ -384,6 +384,9 @@
"IndexerMTeamTpSettingsApiKeyHelpText": "API Key from the Site (Found in User Control Panel => Security => Laboratory)",
"IndexerMTeamTpSettingsFreeleechOnlyHelpText": "Search freeleech releases only",
"IndexerName": "Indexer Name",
+ "IndexerNameTemplate": "Indexer Name Template",
+ "IndexerNameTemplateHelpText": "Template to customize how indexer names appear in external applications. Use {name} for the indexer name and {instance} for the instance name. Default: {name} ({instance})",
+ "IndexerNameTemplateHelpTextWarning": "Template does not contain {name} - indexer names may not be distinguishable",
"IndexerNebulanceSettingsApiKeyHelpText": "API Key from User Settings > Api Keys. Key must have List and Download permissions",
"IndexerNewznabSettingsAdditionalParametersHelpText": "Additional Newznab parameters",
"IndexerNewznabSettingsApiKeyHelpText": "Site API Key",
diff --git a/src/Prowlarr.Api.V1/Config/HostConfigResource.cs b/src/Prowlarr.Api.V1/Config/HostConfigResource.cs
index 4bd0cd10a..062d6b6d5 100644
--- a/src/Prowlarr.Api.V1/Config/HostConfigResource.cs
+++ b/src/Prowlarr.Api.V1/Config/HostConfigResource.cs
@@ -47,6 +47,7 @@ public class HostConfigResource : RestResource
public int BackupRetention { get; set; }
public int HistoryCleanupDays { get; set; }
public bool TrustCgnatIpAddresses { get; set; }
+ public string IndexerNameTemplate { get; set; }
}
public static class HostConfigResourceMapper
@@ -92,7 +93,8 @@ public static HostConfigResource ToResource(this IConfigFileProvider model, ICon
BackupInterval = configService.BackupInterval,
BackupRetention = configService.BackupRetention,
ApplicationUrl = configService.ApplicationUrl,
- HistoryCleanupDays = configService.HistoryCleanupDays
+ HistoryCleanupDays = configService.HistoryCleanupDays,
+ IndexerNameTemplate = configService.IndexerNameTemplate
};
}
}