diff --git a/src/Jackett.Common/Indexers/BaseIndexer.cs b/src/Jackett.Common/Indexers/BaseIndexer.cs index 58d3667e9..36c09f93f 100644 --- a/src/Jackett.Common/Indexers/BaseIndexer.cs +++ b/src/Jackett.Common/Indexers/BaseIndexer.cs @@ -317,15 +317,11 @@ namespace Jackett.Common.Indexers return true; var caps = TorznabCaps; - - if (query.HasSpecifiedCategories) - if (!caps.Categories.SupportsCategories(query.Categories)) - return false; if (caps.TvSearchImdbAvailable && query.IsImdbQuery && query.IsTVSearch) return true; if (caps.MovieSearchImdbAvailable && query.IsImdbQuery && query.IsMovieSearch) return true; - else if (!caps.MovieSearchImdbAvailable && query.IsImdbQuery && query.QueryType != "TorrentPotato") // potato query should always contain imdb+search term + if (!caps.MovieSearchImdbAvailable && query.IsImdbQuery && query.QueryType != "TorrentPotato") // potato query should always contain imdb+search term return false; if (caps.SearchAvailable && query.IsSearch) return true; @@ -349,6 +345,27 @@ namespace Jackett.Common.Indexers return false; } + protected bool CanHandleCategories(TorznabQuery query, bool isMetaIndexer = false) + { + // https://torznab.github.io/spec-1.3-draft/torznab/Specification-v1.3.html#cat-parameter + if (query.HasSpecifiedCategories) + { + var supportedCats = TorznabCaps.Categories.SupportedCategories(query.Categories); + if (supportedCats.Length == 0) + { + if (!isMetaIndexer) + logger.Error($"All categories provided are unsupported in {DisplayName}: {string.Join(",", query.Categories)}"); + return false; + } + if (supportedCats.Length != query.Categories.Length && !isMetaIndexer) + { + var unsupportedCats = query.Categories.Except(supportedCats); + logger.Warn($"Some of the categories provided are unsupported in {DisplayName}: {string.Join(",", unsupportedCats)}"); + } + } + return true; + } + public void Unconfigure() { IsConfigured = false; @@ -358,9 +375,9 @@ namespace Jackett.Common.Indexers public abstract Task ApplyConfiguration(JToken configJson); - public virtual async Task ResultsForQuery(TorznabQuery query) + public virtual async Task ResultsForQuery(TorznabQuery query, bool isMetaIndexer) { - if (!CanHandleQuery(query)) + if (!CanHandleQuery(query) || !CanHandleCategories(query, isMetaIndexer)) return new IndexerResult(this, new ReleaseInfo[0]); try @@ -637,9 +654,9 @@ namespace Jackett.Common.Indexers return releases; } - public override async Task ResultsForQuery(TorznabQuery query) + public override async Task ResultsForQuery(TorznabQuery query, bool isMetaIndexer) { - var result = await base.ResultsForQuery(query); + var result = await base.ResultsForQuery(query, isMetaIndexer); result.Releases = CleanLinks(result.Releases); return result; diff --git a/src/Jackett.Common/Indexers/IIndexer.cs b/src/Jackett.Common/Indexers/IIndexer.cs index dfb0785a0..088dd8cb2 100644 --- a/src/Jackett.Common/Indexers/IIndexer.cs +++ b/src/Jackett.Common/Indexers/IIndexer.cs @@ -51,7 +51,7 @@ namespace Jackett.Common.Indexers void Unconfigure(); - Task ResultsForQuery(TorznabQuery query); + Task ResultsForQuery(TorznabQuery query, bool isMetaIndexer=false); bool CanHandleQuery(TorznabQuery query); } diff --git a/src/Jackett.Common/Indexers/Meta/BaseMetaIndexer.cs b/src/Jackett.Common/Indexers/Meta/BaseMetaIndexer.cs index b219020c6..4eb8982ac 100644 --- a/src/Jackett.Common/Indexers/Meta/BaseMetaIndexer.cs +++ b/src/Jackett.Common/Indexers/Meta/BaseMetaIndexer.cs @@ -46,9 +46,9 @@ namespace Jackett.Common.Indexers.Meta public override Task ApplyConfiguration(JToken configJson) => Task.FromResult(IndexerConfigurationStatus.Completed); - public override async Task ResultsForQuery(TorznabQuery query) + public override async Task ResultsForQuery(TorznabQuery query, bool isMetaIndexer) { - if (!CanHandleQuery(query)) + if (!CanHandleQuery(query) || !CanHandleCategories(query, true)) return new IndexerResult(this, new ReleaseInfo[0]); try @@ -66,11 +66,11 @@ namespace Jackett.Common.Indexers.Meta protected override async Task> PerformQuery(TorznabQuery query) { var indexers = validIndexers; - IEnumerable> supportedTasks = indexers.Where(i => i.CanHandleQuery(query)).Select(i => i.ResultsForQuery(query)).ToList(); // explicit conversion to List to execute LINQ query + IEnumerable> supportedTasks = indexers.Where(i => i.CanHandleQuery(query)).Select(i => i.ResultsForQuery(query, true)).ToList(); // explicit conversion to List to execute LINQ query var fallbackStrategies = fallbackStrategyProvider.FallbackStrategiesForQuery(query); var fallbackQueries = fallbackStrategies.Select(async f => await f.FallbackQueries()).SelectMany(t => t.Result); - var fallbackTasks = fallbackQueries.SelectMany(q => indexers.Where(i => !i.CanHandleQuery(query) && i.CanHandleQuery(q)).Select(i => i.ResultsForQuery(q.Clone()))); + var fallbackTasks = fallbackQueries.SelectMany(q => indexers.Where(i => !i.CanHandleQuery(query) && i.CanHandleQuery(q)).Select(i => i.ResultsForQuery(q.Clone(), true))); var tasks = supportedTasks.Concat(fallbackTasks.ToList()); // explicit conversion to List to execute LINQ query // When there are many indexers used by a metaindexer querying each and every one of them can take very very diff --git a/src/Jackett.Common/Models/DTO/ApiSearch.cs b/src/Jackett.Common/Models/DTO/ApiSearch.cs index 0dcc82077..5760dc24d 100644 --- a/src/Jackett.Common/Models/DTO/ApiSearch.cs +++ b/src/Jackett.Common/Models/DTO/ApiSearch.cs @@ -41,7 +41,6 @@ namespace Jackett.Common.Models.DTO stringQuery.SearchTerm = queryStr; stringQuery.Categories = request.Category ?? new int[0]; - stringQuery.ExpandCatsToSubCats(); // try to build an IMDB Query (tt plus 6 to 8 digits) if (stringQuery.SanitizedSearchTerm.StartsWith("tt") && stringQuery.SanitizedSearchTerm.Length <= 10) @@ -57,7 +56,6 @@ namespace Jackett.Common.Models.DTO Season = stringQuery.Season, Episode = stringQuery.Episode, }; - imdbQuery.ExpandCatsToSubCats(); return imdbQuery; } diff --git a/src/Jackett.Common/Models/DTO/TorrentPotatoRequest.cs b/src/Jackett.Common/Models/DTO/TorrentPotatoRequest.cs index 8bd9718c7..54290a9b9 100644 --- a/src/Jackett.Common/Models/DTO/TorrentPotatoRequest.cs +++ b/src/Jackett.Common/Models/DTO/TorrentPotatoRequest.cs @@ -15,8 +15,6 @@ namespace Jackett.Common.Models.DTO ImdbID = request.Imdbid, QueryType = "TorrentPotato" }; - torznabQuery.ExpandCatsToSubCats(); - return torznabQuery; } } diff --git a/src/Jackett.Common/Models/DTO/TorznabRequest.cs b/src/Jackett.Common/Models/DTO/TorznabRequest.cs index 08251dc46..302a30090 100644 --- a/src/Jackett.Common/Models/DTO/TorznabRequest.cs +++ b/src/Jackett.Common/Models/DTO/TorznabRequest.cs @@ -85,8 +85,6 @@ namespace Jackett.Common.Models.DTO if (!string.IsNullOrWhiteSpace(request.author)) query.Author = request.author; - query.ExpandCatsToSubCats(); - return query; } } diff --git a/src/Jackett.Common/Models/TorznabCapabilitiesCategories.cs b/src/Jackett.Common/Models/TorznabCapabilitiesCategories.cs index 337de9620..940136029 100644 --- a/src/Jackett.Common/Models/TorznabCapabilitiesCategories.cs +++ b/src/Jackett.Common/Models/TorznabCapabilitiesCategories.cs @@ -104,14 +104,13 @@ namespace Jackett.Common.Models return cats; } - public bool SupportsCategories(int[] categories) + public int[] SupportedCategories(int[] categories) { - if (categories == null) - return false; + if (categories == null || categories.Length == 0) + return new int[0]; var subCategories = _torznabCategoryTree.SelectMany(c => c.SubCategories); var allCategories = _torznabCategoryTree.Concat(subCategories); - var supportsCategory = allCategories.Any(i => categories.Any(c => c == i.ID)); - return supportsCategory; + return allCategories.Where(c => categories.Contains(c.ID)).Select(c => c.ID).ToArray(); } public void Concat(TorznabCapabilitiesCategories rhs) diff --git a/src/Jackett.Common/Models/TorznabQuery.cs b/src/Jackett.Common/Models/TorznabQuery.cs index c110a9749..ee8997d01 100644 --- a/src/Jackett.Common/Models/TorznabQuery.cs +++ b/src/Jackett.Common/Models/TorznabQuery.cs @@ -210,24 +210,5 @@ namespace Jackett.Common.Models } return episodeString; } - - public void ExpandCatsToSubCats() - { - if (Categories.Count() == 0) - return; - var newCatList = new List(); - newCatList.AddRange(Categories); - foreach (var cat in Categories) - { - var majorCat = TorznabCatType.AllCats.Where(c => c.ID == cat).FirstOrDefault(); - // If we search for TV we should also search for all sub cats - if (majorCat != null) - { - newCatList.AddRange(majorCat.SubCategories.Select(s => s.ID)); - } - } - - Categories = newCatList.Distinct().ToArray(); - } } } diff --git a/src/Jackett.Server/Controllers/ResultsController.cs b/src/Jackett.Server/Controllers/ResultsController.cs index 12bd4fb5d..3073094ec 100644 --- a/src/Jackett.Server/Controllers/ResultsController.cs +++ b/src/Jackett.Server/Controllers/ResultsController.cs @@ -139,8 +139,9 @@ namespace Jackett.Server.Controllers if (!resultController.CurrentIndexer.CanHandleQuery(resultController.CurrentQuery)) { - context.Result = ResultsController.GetErrorActionResult(context.RouteData, HttpStatusCode.BadRequest, 201, $"{resultController.CurrentIndexer.Id} " + - $"does not support the requested query. Please check the capabilities (t=caps) and make sure the search mode and categories are supported."); + context.Result = ResultsController.GetErrorActionResult(context.RouteData, HttpStatusCode.BadRequest, 201, + $"{resultController.CurrentIndexer.Id} does not support the requested query. " + + "Please check the capabilities (t=caps) and make sure the search mode and parameters are supported."); } } @@ -211,13 +212,11 @@ namespace Jackett.Server.Controllers var manualResult = new ManualSearchResult(); var trackers = IndexerService.GetAllIndexers().ToList().Where(t => t.IsConfigured); if (request.Tracker != null) - { trackers = trackers.Where(t => request.Tracker.Contains(t.Id)); - } - trackers = trackers.Where(t => t.CanHandleQuery(CurrentQuery)); - var tasks = trackers.ToList().Select(t => t.ResultsForQuery(CurrentQuery)).ToList(); + var isMetaIndexer = request.Tracker == null || request.Tracker.Length > 1; + var tasks = trackers.ToList().Select(t => t.ResultsForQuery(CurrentQuery, isMetaIndexer)).ToList(); try { var aggregateTask = Task.WhenAll(tasks); diff --git a/src/Jackett.Test/Common/Models/TorznabCapabilitiesCategoriesTests.cs b/src/Jackett.Test/Common/Models/TorznabCapabilitiesCategoriesTests.cs index 545611bc7..b2f0c2aaa 100644 --- a/src/Jackett.Test/Common/Models/TorznabCapabilitiesCategoriesTests.cs +++ b/src/Jackett.Test/Common/Models/TorznabCapabilitiesCategoriesTests.cs @@ -326,19 +326,30 @@ namespace Jackett.Test.Common.Models } [Test] - public void TestSupportsCategories() + public void TestSupportedCategories() { var tcc = CreateTestDataset(); - Assert.True(tcc.SupportsCategories(new []{ TorznabCatType.Movies.ID })); // parent cat - Assert.True(tcc.SupportsCategories(new []{ TorznabCatType.MoviesSD.ID })); // child cat - Assert.True(tcc.SupportsCategories(new []{ TorznabCatType.Movies.ID, TorznabCatType.MoviesSD.ID })); // parent & child - Assert.True(tcc.SupportsCategories(new []{ 100040 })); // custom cat - Assert.False(tcc.SupportsCategories(new []{ TorznabCatType.Movies3D.ID })); // not supported child cat - Assert.False(tcc.SupportsCategories(new []{ 9999 })); // unknown cat - Assert.False(tcc.SupportsCategories(new []{ 100001 })); // unknown custom cat - Assert.False(tcc.SupportsCategories(new int[]{})); // empty list - Assert.False(tcc.SupportsCategories(null)); // null + Assert.AreEqual( new[] { TorznabCatType.Movies.ID }, // parent cat + tcc.SupportedCategories(new []{ TorznabCatType.Movies.ID })); + Assert.AreEqual( new[] { TorznabCatType.MoviesSD.ID }, // child cat + tcc.SupportedCategories(new []{ TorznabCatType.MoviesSD.ID })); + Assert.AreEqual( new[] { TorznabCatType.Movies.ID, TorznabCatType.MoviesSD.ID }, // parent & child cat + tcc.SupportedCategories(new []{ TorznabCatType.Movies.ID, TorznabCatType.MoviesSD.ID })); + Assert.AreEqual( new[] { 100040 }, // custom cat + tcc.SupportedCategories(new []{ 100040 })); + Assert.AreEqual( new[] { TorznabCatType.Movies.ID }, // mixed good and bad + tcc.SupportedCategories(new []{ TorznabCatType.Movies.ID, 9999 })); + Assert.AreEqual( new int[] {}, // not supported child cat + tcc.SupportedCategories(new []{ TorznabCatType.Movies3D.ID })); + Assert.AreEqual( new int[] {}, // unknown cat + tcc.SupportedCategories(new []{ 9999 })); + Assert.AreEqual( new int[] {}, // unknown custom cat + tcc.SupportedCategories(new []{ 100001 })); + Assert.AreEqual( new int[]{}, // empty list + tcc.SupportedCategories(new int[]{})); + Assert.AreEqual( new int[] {}, // null + tcc.SupportedCategories(null)); } [Test]