mirror of
https://github.com/Prowlarr/Prowlarr
synced 2026-05-07 12:10:20 +02:00
Merge ea5f5fdaea into 18fe4ec495
This commit is contained in:
commit
85432eec13
16 changed files with 505 additions and 0 deletions
|
|
@ -0,0 +1,101 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.IndexerSearch;
|
||||
using NzbDrone.Core.Indexers.Definitions.Cardigann;
|
||||
using NzbDrone.Core.Indexers.Settings;
|
||||
|
||||
namespace NzbDrone.Core.Test.IndexerSearchTests
|
||||
{
|
||||
public class IndexerRawSearchDefinitionBuilderFixture
|
||||
{
|
||||
[Test]
|
||||
public void should_reset_known_search_constraints_on_cloned_settings()
|
||||
{
|
||||
var original = new IndexerDefinition
|
||||
{
|
||||
Id = 1,
|
||||
Name = "test",
|
||||
Implementation = "TestIndexer",
|
||||
Settings = new TestIndexerSettings
|
||||
{
|
||||
BaseUrl = "https://tracker.example",
|
||||
FreeleechOnly = true,
|
||||
UseFreeleechToken = 2,
|
||||
LimitedOnly = true,
|
||||
SearchTypes = new[] { 1, 2 },
|
||||
BaseSettings = new IndexerBaseSettings { QueryLimit = 42 }
|
||||
}
|
||||
};
|
||||
|
||||
var clone = IndexerRawSearchDefinitionBuilder.Build(original);
|
||||
var cloneSettings = (TestIndexerSettings)clone.Settings;
|
||||
var originalSettings = (TestIndexerSettings)original.Settings;
|
||||
|
||||
cloneSettings.FreeleechOnly.Should().BeFalse();
|
||||
cloneSettings.UseFreeleechToken.Should().Be(0);
|
||||
cloneSettings.LimitedOnly.Should().BeFalse();
|
||||
cloneSettings.SearchTypes.Should().BeEmpty();
|
||||
cloneSettings.BaseSettings.QueryLimit.Should().Be(42);
|
||||
|
||||
originalSettings.FreeleechOnly.Should().BeTrue();
|
||||
originalSettings.UseFreeleechToken.Should().Be(2);
|
||||
originalSettings.LimitedOnly.Should().BeTrue();
|
||||
originalSettings.SearchTypes.Should().Equal(1, 2);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_reset_known_cardigann_search_constraint_fields()
|
||||
{
|
||||
var original = new IndexerDefinition
|
||||
{
|
||||
Id = 2,
|
||||
Name = "cardigann-test",
|
||||
Implementation = "Cardigann",
|
||||
Settings = new CardigannSettings
|
||||
{
|
||||
DefinitionFile = "test.yml",
|
||||
BaseUrl = "https://tracker.example",
|
||||
ExtraFieldData = new Dictionary<string, object>
|
||||
{
|
||||
["freeleech"] = true,
|
||||
["useFreeleechToken"] = 2,
|
||||
["someOtherField"] = "keep"
|
||||
}
|
||||
},
|
||||
ExtraFields = new List<SettingsField>
|
||||
{
|
||||
new() { Name = "freeleech", Type = "checkbox", Label = "Freeleech only", Default = "false" },
|
||||
new() { Name = "useFreeleechToken", Type = "select", Label = "Use freeleech token", Default = "0" },
|
||||
new() { Name = "someOtherField", Type = "text", Label = "Other field" }
|
||||
}
|
||||
};
|
||||
|
||||
var clone = IndexerRawSearchDefinitionBuilder.Build(original);
|
||||
var cloneSettings = (CardigannSettings)clone.Settings;
|
||||
var originalSettings = (CardigannSettings)original.Settings;
|
||||
|
||||
cloneSettings.ExtraFieldData["freeleech"].Should().Be(false);
|
||||
cloneSettings.ExtraFieldData["useFreeleechToken"].Should().Be(0);
|
||||
cloneSettings.ExtraFieldData["someOtherField"].Should().Be("keep");
|
||||
|
||||
originalSettings.ExtraFieldData["freeleech"].Should().Be(true);
|
||||
originalSettings.ExtraFieldData["useFreeleechToken"].Should().Be(2);
|
||||
}
|
||||
|
||||
private class TestIndexerSettings : NoAuthTorrentBaseSettings
|
||||
{
|
||||
public bool FreeleechOnly { get; set; }
|
||||
|
||||
public int UseFreeleechToken { get; set; }
|
||||
|
||||
[SearchConstraint(SearchConstraintResetBehavior.False)]
|
||||
public bool LimitedOnly { get; set; }
|
||||
|
||||
[SearchConstraint]
|
||||
public IEnumerable<int> SearchTypes { get; set; } = Array.Empty<int>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@
|
|||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.Indexers.Definitions;
|
||||
using NzbDrone.Core.IndexerSearch;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
|
|
@ -87,5 +88,61 @@ public void should_normalize_imdbid_tv_search_criteria(string input, string expe
|
|||
criteria.Count.Should().Be(1);
|
||||
criteria[0].ImdbId.Should().Be(expected);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_clone_indexer_definition_for_raw_search_mode()
|
||||
{
|
||||
var originalDefinition = new IndexerDefinition
|
||||
{
|
||||
Id = 1,
|
||||
Name = "BeyondHD",
|
||||
Implementation = nameof(BeyondHD),
|
||||
Settings = new BeyondHDSettings
|
||||
{
|
||||
BaseUrl = "https://tracker.example",
|
||||
FreeleechOnly = true,
|
||||
LimitedOnly = true,
|
||||
RefundOnly = true,
|
||||
RewindOnly = true,
|
||||
SearchTypes = new[] { (int)BeyondHDSearchType.TypeUhd100 }
|
||||
}
|
||||
};
|
||||
|
||||
_mockIndexer.SetupGet(s => s.Definition).Returns(originalDefinition);
|
||||
|
||||
IndexerDefinition rawDefinition = null;
|
||||
|
||||
Mocker.GetMock<IIndexerFactory>()
|
||||
.Setup(s => s.GetInstance(It.IsAny<IndexerDefinition>()))
|
||||
.Callback<IndexerDefinition>(definition => rawDefinition = definition)
|
||||
.Returns(_mockIndexer.Object);
|
||||
|
||||
var request = new NewznabRequest
|
||||
{
|
||||
t = "movie",
|
||||
searchMode = "raw"
|
||||
};
|
||||
|
||||
Subject.Search(request, new List<int> { 1 }, false).GetAwaiter().GetResult();
|
||||
|
||||
rawDefinition.Should().NotBeNull();
|
||||
rawDefinition.Should().NotBeSameAs(originalDefinition);
|
||||
rawDefinition.Settings.Should().NotBeSameAs(originalDefinition.Settings);
|
||||
|
||||
var rawSettings = (BeyondHDSettings)rawDefinition.Settings;
|
||||
var originalSettings = (BeyondHDSettings)originalDefinition.Settings;
|
||||
|
||||
rawSettings.FreeleechOnly.Should().BeFalse();
|
||||
rawSettings.LimitedOnly.Should().BeFalse();
|
||||
rawSettings.RefundOnly.Should().BeFalse();
|
||||
rawSettings.RewindOnly.Should().BeFalse();
|
||||
rawSettings.SearchTypes.Should().BeEmpty();
|
||||
|
||||
originalSettings.FreeleechOnly.Should().BeTrue();
|
||||
originalSettings.LimitedOnly.Should().BeTrue();
|
||||
originalSettings.RefundOnly.Should().BeTrue();
|
||||
originalSettings.RewindOnly.Should().BeTrue();
|
||||
originalSettings.SearchTypes.Should().ContainSingle().Which.Should().Be((int)BeyondHDSearchType.TypeUhd100);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ public abstract class SearchCriteriaBase
|
|||
public long? MaxSize { get; set; }
|
||||
public string Source { get; set; }
|
||||
public string Host { get; set; }
|
||||
public IndexerSearchMode SearchMode { get; set; }
|
||||
|
||||
public override string ToString() => $"{SearchQuery}, Offset: {Offset ?? 0}, Limit: {Limit ?? 0}, Categories: [{string.Join(", ", Categories)}]";
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,239 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Newtonsoft.Json;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.Indexers.Definitions.Cardigann;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
|
||||
namespace NzbDrone.Core.IndexerSearch
|
||||
{
|
||||
internal static class IndexerRawSearchDefinitionBuilder
|
||||
{
|
||||
public static IndexerDefinition Build(IndexerDefinition definition)
|
||||
{
|
||||
if (definition?.Settings == null)
|
||||
{
|
||||
return definition;
|
||||
}
|
||||
|
||||
var clonedSettings = CloneSettings(definition.Settings);
|
||||
ApplyConstraintOverrides(clonedSettings);
|
||||
|
||||
var clonedDefinition = CloneDefinition(definition, clonedSettings);
|
||||
|
||||
if (clonedSettings is CardigannSettings cardigannSettings)
|
||||
{
|
||||
ApplyCardigannOverrides(cardigannSettings, clonedDefinition.ExtraFields);
|
||||
}
|
||||
|
||||
return clonedDefinition;
|
||||
}
|
||||
|
||||
private static IProviderConfig CloneSettings(IProviderConfig settings)
|
||||
{
|
||||
var serialized = JsonConvert.SerializeObject(settings);
|
||||
return (IProviderConfig)JsonConvert.DeserializeObject(serialized, settings.GetType());
|
||||
}
|
||||
|
||||
private static IndexerDefinition CloneDefinition(IndexerDefinition definition, IProviderConfig settings)
|
||||
{
|
||||
return new IndexerDefinition
|
||||
{
|
||||
Id = definition.Id,
|
||||
Name = definition.Name,
|
||||
ImplementationName = definition.ImplementationName,
|
||||
Implementation = definition.Implementation,
|
||||
ConfigContract = definition.ConfigContract,
|
||||
Enable = definition.Enable,
|
||||
Message = definition.Message,
|
||||
Tags = definition.Tags != null ? new HashSet<int>(definition.Tags) : new HashSet<int>(),
|
||||
Settings = settings,
|
||||
IndexerUrls = definition.IndexerUrls,
|
||||
LegacyUrls = definition.LegacyUrls,
|
||||
Description = definition.Description,
|
||||
Encoding = definition.Encoding,
|
||||
Language = definition.Language,
|
||||
Protocol = definition.Protocol,
|
||||
Privacy = definition.Privacy,
|
||||
SupportsRss = definition.SupportsRss,
|
||||
SupportsSearch = definition.SupportsSearch,
|
||||
SupportsRedirect = definition.SupportsRedirect,
|
||||
SupportsPagination = definition.SupportsPagination,
|
||||
Capabilities = definition.Capabilities,
|
||||
Priority = definition.Priority,
|
||||
Redirect = definition.Redirect,
|
||||
DownloadClientId = definition.DownloadClientId,
|
||||
Added = definition.Added,
|
||||
AppProfileId = definition.AppProfileId,
|
||||
AppProfile = definition.AppProfile,
|
||||
ExtraFields = definition.ExtraFields?.Select(CloneExtraField).ToList() ?? new List<SettingsField>()
|
||||
};
|
||||
}
|
||||
|
||||
private static SettingsField CloneExtraField(SettingsField field)
|
||||
{
|
||||
return new SettingsField
|
||||
{
|
||||
Name = field.Name,
|
||||
Type = field.Type,
|
||||
Label = field.Label,
|
||||
Default = field.Default,
|
||||
Defaults = field.Defaults?.ToArray(),
|
||||
Options = field.Options != null ? new Dictionary<string, string>(field.Options) : null
|
||||
};
|
||||
}
|
||||
|
||||
private static void ApplyConstraintOverrides(object target)
|
||||
{
|
||||
if (target == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var targetType = target.GetType();
|
||||
object defaults = null;
|
||||
|
||||
try
|
||||
{
|
||||
defaults = Activator.CreateInstance(targetType);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Best-effort only. If a type cannot be constructed we can still apply explicit false/null resets.
|
||||
}
|
||||
|
||||
foreach (var property in targetType.GetProperties(BindingFlags.Instance | BindingFlags.Public))
|
||||
{
|
||||
if (!property.CanRead || !property.CanWrite || property.GetIndexParameters().Length > 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var currentValue = property.GetValue(target);
|
||||
var defaultValue = defaults != null ? property.GetValue(defaults) : null;
|
||||
|
||||
if (TryGetResetBehavior(property, out var resetBehavior))
|
||||
{
|
||||
property.SetValue(target, BuildResetValue(property.PropertyType, resetBehavior, defaultValue));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ShouldRecurse(property.PropertyType, currentValue))
|
||||
{
|
||||
ApplyConstraintOverrides(currentValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TryGetResetBehavior(PropertyInfo property, out SearchConstraintResetBehavior resetBehavior)
|
||||
{
|
||||
var explicitAttribute = property.GetCustomAttribute<SearchConstraintAttribute>();
|
||||
if (explicitAttribute != null)
|
||||
{
|
||||
resetBehavior = explicitAttribute.ResetBehavior;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (property.Name.EndsWith("Only", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
resetBehavior = property.PropertyType == typeof(bool) || property.PropertyType == typeof(bool?)
|
||||
? SearchConstraintResetBehavior.False
|
||||
: SearchConstraintResetBehavior.Default;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (property.Name.Contains("UseFreeleechToken", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
resetBehavior = SearchConstraintResetBehavior.Default;
|
||||
return true;
|
||||
}
|
||||
|
||||
resetBehavior = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static object BuildResetValue(Type propertyType, SearchConstraintResetBehavior resetBehavior, object defaultValue)
|
||||
{
|
||||
return resetBehavior switch
|
||||
{
|
||||
SearchConstraintResetBehavior.False when propertyType == typeof(bool?) => (bool?)false,
|
||||
SearchConstraintResetBehavior.False => false,
|
||||
SearchConstraintResetBehavior.Empty => string.Empty,
|
||||
SearchConstraintResetBehavior.Null => null,
|
||||
_ => defaultValue ?? (propertyType.IsValueType ? Activator.CreateInstance(propertyType) : null)
|
||||
};
|
||||
}
|
||||
|
||||
private static bool ShouldRecurse(Type propertyType, object currentValue)
|
||||
{
|
||||
if (currentValue == null || propertyType == typeof(string))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (typeof(IDictionary).IsAssignableFrom(propertyType) || typeof(IEnumerable).IsAssignableFrom(propertyType))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return propertyType.IsClass;
|
||||
}
|
||||
|
||||
private static void ApplyCardigannOverrides(CardigannSettings settings, List<SettingsField> extraFields)
|
||||
{
|
||||
if (settings.ExtraFieldData == null || extraFields == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var field in extraFields)
|
||||
{
|
||||
if (!ShouldResetCardigannField(field))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
settings.ExtraFieldData[field.Name] = GetCardigannResetValue(field);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool ShouldResetCardigannField(SettingsField field)
|
||||
{
|
||||
var combined = $"{field.Name} {field.Label}".ToLowerInvariant();
|
||||
|
||||
return combined.Contains("freeleech") ||
|
||||
combined.Contains("freeload") ||
|
||||
combined.Contains("limited") ||
|
||||
(combined.Contains("token") && combined.Contains("free"));
|
||||
}
|
||||
|
||||
private static object GetCardigannResetValue(SettingsField field)
|
||||
{
|
||||
if (string.Equals(field.Type, "checkbox", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (field.Default.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
if (int.TryParse(field.Default, out var number))
|
||||
{
|
||||
return number;
|
||||
}
|
||||
|
||||
if (bool.TryParse(field.Default, out var boolean))
|
||||
{
|
||||
return boolean;
|
||||
}
|
||||
|
||||
return field.Default;
|
||||
}
|
||||
|
||||
return string.Equals(field.Type, "select", StringComparison.OrdinalIgnoreCase) ? string.Empty : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
29
src/NzbDrone.Core/IndexerSearch/IndexerSearchMode.cs
Normal file
29
src/NzbDrone.Core/IndexerSearch/IndexerSearchMode.cs
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
using NzbDrone.Common.Extensions;
|
||||
|
||||
namespace NzbDrone.Core.IndexerSearch
|
||||
{
|
||||
public enum IndexerSearchMode
|
||||
{
|
||||
Default = 0,
|
||||
Raw = 1
|
||||
}
|
||||
|
||||
public static class IndexerSearchModeParser
|
||||
{
|
||||
public static IndexerSearchMode Parse(string value)
|
||||
{
|
||||
if (value.IsNullOrWhiteSpace())
|
||||
{
|
||||
return IndexerSearchMode.Default;
|
||||
}
|
||||
|
||||
return value.Trim().ToLowerInvariant() switch
|
||||
{
|
||||
"raw" => IndexerSearchMode.Raw,
|
||||
"normal" => IndexerSearchMode.Default,
|
||||
"default" => IndexerSearchMode.Default,
|
||||
_ => IndexerSearchMode.Default
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -42,6 +42,7 @@ public class NewznabRequest
|
|||
public string source { get; set; }
|
||||
public string host { get; set; }
|
||||
public string server { get; set; }
|
||||
public string searchMode { get; set; }
|
||||
|
||||
public void QueryToParams()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -150,6 +150,7 @@ private TSpec Get<TSpec>(NewznabRequest query, List<int> indexerIds, bool intera
|
|||
spec.MaxSize = query.maxsize;
|
||||
spec.Source = query.source;
|
||||
spec.Host = query.host;
|
||||
spec.SearchMode = IndexerSearchModeParser.Parse(query.searchMode);
|
||||
|
||||
spec.IndexerIds = indexerIds;
|
||||
|
||||
|
|
@ -175,6 +176,11 @@ private async Task<IList<ReleaseInfo>> Dispatch(Func<IIndexer, Task<IndexerPagea
|
|||
}
|
||||
}
|
||||
|
||||
if (criteriaBase.SearchMode == IndexerSearchMode.Raw)
|
||||
{
|
||||
indexers = indexers.Select(CreateRawSearchIndexer).ToList();
|
||||
}
|
||||
|
||||
if (criteriaBase.Categories is { Length: > 0 })
|
||||
{
|
||||
// Only query supported indexers
|
||||
|
|
@ -201,6 +207,17 @@ private async Task<IList<ReleaseInfo>> Dispatch(Func<IIndexer, Task<IndexerPagea
|
|||
return reports;
|
||||
}
|
||||
|
||||
private IIndexer CreateRawSearchIndexer(IIndexer indexer)
|
||||
{
|
||||
if (indexer.Definition is not IndexerDefinition definition)
|
||||
{
|
||||
return indexer;
|
||||
}
|
||||
|
||||
var rawDefinition = IndexerRawSearchDefinitionBuilder.Build(definition);
|
||||
return _indexerFactory.GetInstance(rawDefinition);
|
||||
}
|
||||
|
||||
private async Task<IList<ReleaseInfo>> DispatchIndexer(Func<IIndexer, Task<IndexerPageableQueryResult>> searchAction, IIndexer indexer, SearchCriteriaBase criteriaBase)
|
||||
{
|
||||
if (_indexerLimitService.AtQueryLimit((IndexerDefinition)indexer.Definition))
|
||||
|
|
|
|||
|
|
@ -389,6 +389,7 @@ public BeyondHDSettings()
|
|||
[FieldDefinition(7, Label = "IndexerBeyondHDSettingsRewindOnly", Type = FieldType.Checkbox, HelpText = "IndexerBeyondHDSettingsRewindOnlyHelpText")]
|
||||
public bool RewindOnly { get; set; }
|
||||
|
||||
[SearchConstraint]
|
||||
[FieldDefinition(8, Label = "IndexerBeyondHDSettingsSearchTypes", Type = FieldType.Select, SelectOptions = typeof(BeyondHDSearchType), HelpText = "IndexerBeyondHDSettingsSearchTypesHelpText", Advanced = true)]
|
||||
public IEnumerable<int> SearchTypes { get; set; }
|
||||
|
||||
|
|
|
|||
|
|
@ -342,6 +342,7 @@ public TorrentSyndikatSettings()
|
|||
[FieldDefinition(3, Label = "Products Only", Type = FieldType.Checkbox, HelpText = "Limit search to torrents linked to a product")]
|
||||
public bool ProductsOnly { get; set; }
|
||||
|
||||
[SearchConstraint]
|
||||
[FieldDefinition(4, Label = "Release Types", Type = FieldType.Select, SelectOptions = typeof(TorrentSyndikatReleaseTypes))]
|
||||
public IEnumerable<int> ReleaseTypes { get; set; }
|
||||
|
||||
|
|
|
|||
23
src/NzbDrone.Core/Indexers/SearchConstraintAttribute.cs
Normal file
23
src/NzbDrone.Core/Indexers/SearchConstraintAttribute.cs
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
using System;
|
||||
|
||||
namespace NzbDrone.Core.Indexers
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
|
||||
public sealed class SearchConstraintAttribute : Attribute
|
||||
{
|
||||
public SearchConstraintAttribute(SearchConstraintResetBehavior resetBehavior = SearchConstraintResetBehavior.Default)
|
||||
{
|
||||
ResetBehavior = resetBehavior;
|
||||
}
|
||||
|
||||
public SearchConstraintResetBehavior ResetBehavior { get; }
|
||||
}
|
||||
|
||||
public enum SearchConstraintResetBehavior
|
||||
{
|
||||
Default,
|
||||
False,
|
||||
Empty,
|
||||
Null
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
using FluentAssertions;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Api.V1.Indexers;
|
||||
|
||||
namespace Prowlarr.Api.V1.Test.Indexers
|
||||
{
|
||||
public class NewznabControllerFixture
|
||||
{
|
||||
[TestCase("/api/v1/indexer/12/newznab", true)]
|
||||
[TestCase("/12/api", false)]
|
||||
[TestCase("/api/v1/indexer/12/download", false)]
|
||||
public void should_only_allow_extended_search_parameters_on_prowlarr_newznab_route(string path, bool expected)
|
||||
{
|
||||
NewznabController.SupportsExtendedSearchParameters(new PathString(path)).Should().Be(expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@
|
|||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\NzbDrone.Core\Prowlarr.Core.csproj" />
|
||||
<ProjectReference Include="..\Prowlarr.Api.V1\Prowlarr.Api.V1.csproj" />
|
||||
<ProjectReference Include="..\NzbDrone.Test.Common\Prowlarr.Test.Common.csproj" />
|
||||
<ProjectReference Include="..\Prowlarr.Http\Prowlarr.Http.csproj" />
|
||||
</ItemGroup>
|
||||
|
|
|
|||
3
src/Prowlarr.Api.V1/AssemblyInfo.cs
Normal file
3
src/Prowlarr.Api.V1/AssemblyInfo.cs
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("Prowlarr.Api.V1.Test")]
|
||||
|
|
@ -58,6 +58,11 @@ public NewznabController(IndexerFactory indexerFactory,
|
|||
[HttpGet("{id:int}/api")]
|
||||
public async Task<IActionResult> GetNewznabResponse(int id, [FromQuery] NewznabRequest request)
|
||||
{
|
||||
if (!SupportsExtendedSearchParameters(Request.Path))
|
||||
{
|
||||
request.searchMode = null;
|
||||
}
|
||||
|
||||
var requestType = request.t;
|
||||
request.source = Request.GetSource();
|
||||
request.server = Request.GetServerUrl();
|
||||
|
|
@ -206,6 +211,12 @@ public async Task<IActionResult> GetNewznabResponse(int id, [FromQuery] NewznabR
|
|||
}
|
||||
}
|
||||
|
||||
internal static bool SupportsExtendedSearchParameters(PathString path)
|
||||
{
|
||||
return path.Value?.StartsWith("/api/v1/indexer/", StringComparison.OrdinalIgnoreCase) == true &&
|
||||
path.Value.EndsWith("/newznab", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
[HttpGet("/api/v1/indexer/{id:int}/download")]
|
||||
[HttpGet("{id:int}/download")]
|
||||
public async Task<object> GetDownload(int id, string link, string file)
|
||||
|
|
|
|||
|
|
@ -163,6 +163,7 @@ private async Task<List<ReleaseResource>> GetSearchReleases([FromQuery] SearchRe
|
|||
cat = string.Join(",", payload.Categories),
|
||||
server = Request.GetServerUrl(),
|
||||
host = Request.GetHostName(),
|
||||
searchMode = payload.SearchMode,
|
||||
limit = payload.Limit,
|
||||
offset = payload.Offset
|
||||
};
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ public SearchResource()
|
|||
|
||||
public string Query { get; set; }
|
||||
public string Type { get; set; }
|
||||
public string SearchMode { get; set; }
|
||||
public List<int> IndexerIds { get; set; }
|
||||
public List<int> Categories { get; set; }
|
||||
public int? Limit { get; set; }
|
||||
|
|
|
|||
Loading…
Reference in a new issue