New: Newznab/Torznab categories dropdown with indexer provided category names

This commit is contained in:
Taloth 2020-10-08 23:33:13 +02:00 committed by Qstick
commit 9b1bbaef02
22 changed files with 429 additions and 36 deletions

View file

@ -27,7 +27,7 @@ namespace Lidarr.Api.V1
Get("schema", x => GetTemplates());
Post("test", x => Test(ReadResourceFromRequest(true)));
Post("testall", x => TestAll());
Post("action/{action}", x => RequestAction(x.action, ReadResourceFromRequest(true)));
Post("action/{action}", x => RequestAction(x.action, ReadResourceFromRequest(true, true)));
GetResourceAll = GetAll;
GetResourceById = GetProviderById;

View file

@ -14,6 +14,7 @@ namespace Lidarr.Http.ClientSchema
public string Type { get; set; }
public bool Advanced { get; set; }
public List<SelectOption> SelectOptions { get; set; }
public string SelectOptionsProviderAction { get; set; }
public string Section { get; set; }
public string Hidden { get; set; }

View file

@ -104,7 +104,14 @@ namespace Lidarr.Http.ClientSchema
if (fieldAttribute.Type == FieldType.Select)
{
field.SelectOptions = GetSelectOptions(fieldAttribute.SelectOptions);
if (fieldAttribute.SelectOptionsProviderAction.IsNotNullOrWhiteSpace())
{
field.SelectOptionsProviderAction = fieldAttribute.SelectOptionsProviderAction;
}
else
{
field.SelectOptions = GetSelectOptions(fieldAttribute.SelectOptions);
}
}
if (fieldAttribute.Hidden != HiddenType.Visible)

View file

@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using FluentValidation;
using FluentValidation.Results;
using Lidarr.Http.Extensions;
using Nancy;
using Nancy.Responses.Negotiation;
@ -224,7 +225,7 @@ namespace Lidarr.Http.REST
return Negotiate.WithModel(model).WithStatusCode(statusCode);
}
protected TResource ReadResourceFromRequest(bool skipValidate = false)
protected TResource ReadResourceFromRequest(bool skipValidate = false, bool skipSharedValidate = false)
{
var resource = new TResource();
@ -242,7 +243,12 @@ namespace Lidarr.Http.REST
throw new BadRequestException("Request body can't be empty");
}
var errors = SharedValidator.Validate(resource).Errors.ToList();
var errors = new List<ValidationFailure>();
if (!skipSharedValidate)
{
errors.AddRange(SharedValidator.Validate(resource).Errors);
}
if (Request.Method.Equals("POST", StringComparison.InvariantCultureIgnoreCase) && !skipValidate && !Request.Url.Path.EndsWith("/test", StringComparison.InvariantCultureIgnoreCase))
{

View file

@ -19,6 +19,7 @@ namespace NzbDrone.Core.Annotations
public FieldType Type { get; set; }
public bool Advanced { get; set; }
public Type SelectOptions { get; set; }
public string SelectOptionsProviderAction { get; set; }
public string Section { get; set; }
public HiddenType Hidden { get; set; }
public PrivacyLevel Privacy { get; set; }
@ -38,6 +39,15 @@ namespace NzbDrone.Core.Annotations
public string Hint { get; set; }
}
public class FieldSelectOption
{
public int Value { get; set; }
public string Name { get; set; }
public int Order { get; set; }
public string Hint { get; set; }
public int? ParentValue { get; set; }
}
public enum FieldType
{
Textbox,

View file

@ -294,7 +294,14 @@ namespace NzbDrone.Core.Indexers
{
var parser = GetParser();
var generator = GetRequestGenerator();
var releases = FetchPage(generator.GetRecentRequests().GetAllTiers().First().First(), parser);
var firstRequest = generator.GetRecentRequests().GetAllTiers().FirstOrDefault()?.FirstOrDefault();
if (firstRequest == null)
{
return new ValidationFailure(string.Empty, "No rss feed query available. This may be an issue with the indexer or your indexer category settings.");
}
var releases = FetchPage(firstRequest, parser);
if (releases.Empty())
{

View file

@ -138,5 +138,31 @@ namespace NzbDrone.Core.Indexers.Newznab
return new ValidationFailure(string.Empty, "Unable to connect to indexer, check the log for more details");
}
}
public override object RequestAction(string action, IDictionary<string, string> query)
{
if (action == "newznabCategories")
{
List<NewznabCategory> categories = null;
try
{
if (Settings.BaseUrl.IsNotNullOrWhiteSpace() && Settings.ApiPath.IsNotNullOrWhiteSpace())
{
categories = _capabilitiesProvider.GetCapabilities(Settings).Categories;
}
}
catch
{
// Use default categories
}
return new
{
options = NewznabCategoryFieldOptionsConverter.GetFieldSelectOptions(categories)
};
}
return base.RequestAction(action, query);
}
}
}

View file

@ -48,6 +48,7 @@ namespace NzbDrone.Core.Indexers.Newznab
}
var request = new HttpRequest(url, HttpAccept.Rss);
request.AllowAutoRedirect = true;
HttpResponse response;

View file

@ -0,0 +1,69 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Core.Annotations;
namespace NzbDrone.Core.Indexers.Newznab
{
public static class NewznabCategoryFieldOptionsConverter
{
public static List<FieldSelectOption> GetFieldSelectOptions(List<NewznabCategory> categories)
{
// Ignore categories not relevant for Lidarr
var ignoreCategories = new[] { 0, 1000, 2000, 4000, 5000, 6000, 7000 };
var result = new List<FieldSelectOption>();
if (categories == null)
{
// Fetching categories failed, use default Newznab categories
categories = new List<NewznabCategory>();
categories.Add(new NewznabCategory
{
Id = 3000,
Name = "Music",
Subcategories = new List<NewznabCategory>
{
new NewznabCategory { Id = 3040, Name = "Loseless" },
new NewznabCategory { Id = 3010, Name = "MP3" },
new NewznabCategory { Id = 3050, Name = "Other" },
new NewznabCategory { Id = 3030, Name = "Audiobook" }
}
});
}
foreach (var category in categories)
{
if (ignoreCategories.Contains(category.Id))
{
continue;
}
result.Add(new FieldSelectOption
{
Value = category.Id,
Name = category.Name,
Hint = $"({category.Id})"
});
if (category.Subcategories != null)
{
foreach (var subcat in category.Subcategories)
{
result.Add(new FieldSelectOption
{
Value = subcat.Id,
Name = subcat.Name,
Hint = $"({subcat.Id})",
ParentValue = category.Id
});
}
}
}
result.Sort((l, r) => l.Value.CompareTo(r.Value));
return result;
}
}
}

View file

@ -58,7 +58,7 @@ namespace NzbDrone.Core.Indexers.Newznab
public NewznabSettings()
{
ApiPath = "/api";
Categories = new[] { 3000, 3010, 3020, 3030, 3040 };
Categories = new[] { 3000, 3010, 3030, 3040 };
}
[FieldDefinition(0, Label = "URL")]
@ -70,7 +70,7 @@ namespace NzbDrone.Core.Indexers.Newznab
[FieldDefinition(2, Label = "API Key", Privacy = PrivacyLevel.ApiKey)]
public string ApiKey { get; set; }
[FieldDefinition(3, Label = "Categories", HelpText = "Comma Separated list, leave blank to disable standard/daily shows", Advanced = true)]
[FieldDefinition(3, Label = "Categories", Type = FieldType.Select, SelectOptionsProviderAction = "newznabCategories", HelpText = "Comma Separated list")]
public IEnumerable<int> Categories { get; set; }
[FieldDefinition(4, Type = FieldType.Number, Label = "Early Download Limit", HelpText = "Time before release date Lidarr will download from this indexer, empty is no limit", Unit = "days", Advanced = true)]

View file

@ -106,5 +106,28 @@ namespace NzbDrone.Core.Indexers.Torznab
return new ValidationFailure(string.Empty, "Unable to connect to indexer, check the log for more details");
}
}
public override object RequestAction(string action, IDictionary<string, string> query)
{
if (action == "newznabCategories")
{
List<NewznabCategory> categories = null;
try
{
categories = _capabilitiesProvider.GetCapabilities(Settings).Categories;
}
catch
{
// Use default categories
}
return new
{
options = NewznabCategoryFieldOptionsConverter.GetFieldSelectOptions(categories)
};
}
return base.RequestAction(action, query);
}
}
}