core: follow torznab specs about categories. resolves #10120 (#10147)

This commit is contained in:
Diego Heras 2020-11-08 23:27:54 +01:00 committed by GitHub
parent 10c8e33715
commit 2030d9cf13
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 61 additions and 60 deletions

View File

@ -317,15 +317,11 @@ namespace Jackett.Common.Indexers
return true; return true;
var caps = TorznabCaps; var caps = TorznabCaps;
if (query.HasSpecifiedCategories)
if (!caps.Categories.SupportsCategories(query.Categories))
return false;
if (caps.TvSearchImdbAvailable && query.IsImdbQuery && query.IsTVSearch) if (caps.TvSearchImdbAvailable && query.IsImdbQuery && query.IsTVSearch)
return true; return true;
if (caps.MovieSearchImdbAvailable && query.IsImdbQuery && query.IsMovieSearch) if (caps.MovieSearchImdbAvailable && query.IsImdbQuery && query.IsMovieSearch)
return true; 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; return false;
if (caps.SearchAvailable && query.IsSearch) if (caps.SearchAvailable && query.IsSearch)
return true; return true;
@ -349,6 +345,27 @@ namespace Jackett.Common.Indexers
return false; 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() public void Unconfigure()
{ {
IsConfigured = false; IsConfigured = false;
@ -358,9 +375,9 @@ namespace Jackett.Common.Indexers
public abstract Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson); public abstract Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson);
public virtual async Task<IndexerResult> ResultsForQuery(TorznabQuery query) public virtual async Task<IndexerResult> ResultsForQuery(TorznabQuery query, bool isMetaIndexer)
{ {
if (!CanHandleQuery(query)) if (!CanHandleQuery(query) || !CanHandleCategories(query, isMetaIndexer))
return new IndexerResult(this, new ReleaseInfo[0]); return new IndexerResult(this, new ReleaseInfo[0]);
try try
@ -637,9 +654,9 @@ namespace Jackett.Common.Indexers
return releases; return releases;
} }
public override async Task<IndexerResult> ResultsForQuery(TorznabQuery query) public override async Task<IndexerResult> ResultsForQuery(TorznabQuery query, bool isMetaIndexer)
{ {
var result = await base.ResultsForQuery(query); var result = await base.ResultsForQuery(query, isMetaIndexer);
result.Releases = CleanLinks(result.Releases); result.Releases = CleanLinks(result.Releases);
return result; return result;

View File

@ -51,7 +51,7 @@ namespace Jackett.Common.Indexers
void Unconfigure(); void Unconfigure();
Task<IndexerResult> ResultsForQuery(TorznabQuery query); Task<IndexerResult> ResultsForQuery(TorznabQuery query, bool isMetaIndexer=false);
bool CanHandleQuery(TorznabQuery query); bool CanHandleQuery(TorznabQuery query);
} }

View File

@ -46,9 +46,9 @@ namespace Jackett.Common.Indexers.Meta
public override Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson) => Task.FromResult(IndexerConfigurationStatus.Completed); public override Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson) => Task.FromResult(IndexerConfigurationStatus.Completed);
public override async Task<IndexerResult> ResultsForQuery(TorznabQuery query) public override async Task<IndexerResult> ResultsForQuery(TorznabQuery query, bool isMetaIndexer)
{ {
if (!CanHandleQuery(query)) if (!CanHandleQuery(query) || !CanHandleCategories(query, true))
return new IndexerResult(this, new ReleaseInfo[0]); return new IndexerResult(this, new ReleaseInfo[0]);
try try
@ -66,11 +66,11 @@ namespace Jackett.Common.Indexers.Meta
protected override async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query) protected override async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
{ {
var indexers = validIndexers; var indexers = validIndexers;
IEnumerable<Task<IndexerResult>> supportedTasks = indexers.Where(i => i.CanHandleQuery(query)).Select(i => i.ResultsForQuery(query)).ToList(); // explicit conversion to List to execute LINQ query IEnumerable<Task<IndexerResult>> 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 fallbackStrategies = fallbackStrategyProvider.FallbackStrategiesForQuery(query);
var fallbackQueries = fallbackStrategies.Select(async f => await f.FallbackQueries()).SelectMany(t => t.Result); 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 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 // When there are many indexers used by a metaindexer querying each and every one of them can take very very

View File

@ -41,7 +41,6 @@ namespace Jackett.Common.Models.DTO
stringQuery.SearchTerm = queryStr; stringQuery.SearchTerm = queryStr;
stringQuery.Categories = request.Category ?? new int[0]; stringQuery.Categories = request.Category ?? new int[0];
stringQuery.ExpandCatsToSubCats();
// try to build an IMDB Query (tt plus 6 to 8 digits) // try to build an IMDB Query (tt plus 6 to 8 digits)
if (stringQuery.SanitizedSearchTerm.StartsWith("tt") && stringQuery.SanitizedSearchTerm.Length <= 10) if (stringQuery.SanitizedSearchTerm.StartsWith("tt") && stringQuery.SanitizedSearchTerm.Length <= 10)
@ -57,7 +56,6 @@ namespace Jackett.Common.Models.DTO
Season = stringQuery.Season, Season = stringQuery.Season,
Episode = stringQuery.Episode, Episode = stringQuery.Episode,
}; };
imdbQuery.ExpandCatsToSubCats();
return imdbQuery; return imdbQuery;
} }

View File

@ -15,8 +15,6 @@ namespace Jackett.Common.Models.DTO
ImdbID = request.Imdbid, ImdbID = request.Imdbid,
QueryType = "TorrentPotato" QueryType = "TorrentPotato"
}; };
torznabQuery.ExpandCatsToSubCats();
return torznabQuery; return torznabQuery;
} }
} }

View File

@ -85,8 +85,6 @@ namespace Jackett.Common.Models.DTO
if (!string.IsNullOrWhiteSpace(request.author)) if (!string.IsNullOrWhiteSpace(request.author))
query.Author = request.author; query.Author = request.author;
query.ExpandCatsToSubCats();
return query; return query;
} }
} }

View File

@ -104,14 +104,13 @@ namespace Jackett.Common.Models
return cats; return cats;
} }
public bool SupportsCategories(int[] categories) public int[] SupportedCategories(int[] categories)
{ {
if (categories == null) if (categories == null || categories.Length == 0)
return false; return new int[0];
var subCategories = _torznabCategoryTree.SelectMany(c => c.SubCategories); var subCategories = _torznabCategoryTree.SelectMany(c => c.SubCategories);
var allCategories = _torznabCategoryTree.Concat(subCategories); var allCategories = _torznabCategoryTree.Concat(subCategories);
var supportsCategory = allCategories.Any(i => categories.Any(c => c == i.ID)); return allCategories.Where(c => categories.Contains(c.ID)).Select(c => c.ID).ToArray();
return supportsCategory;
} }
public void Concat(TorznabCapabilitiesCategories rhs) public void Concat(TorznabCapabilitiesCategories rhs)

View File

@ -210,24 +210,5 @@ namespace Jackett.Common.Models
} }
return episodeString; return episodeString;
} }
public void ExpandCatsToSubCats()
{
if (Categories.Count() == 0)
return;
var newCatList = new List<int>();
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();
}
} }
} }

View File

@ -139,8 +139,9 @@ namespace Jackett.Server.Controllers
if (!resultController.CurrentIndexer.CanHandleQuery(resultController.CurrentQuery)) if (!resultController.CurrentIndexer.CanHandleQuery(resultController.CurrentQuery))
{ {
context.Result = ResultsController.GetErrorActionResult(context.RouteData, HttpStatusCode.BadRequest, 201, $"{resultController.CurrentIndexer.Id} " + context.Result = ResultsController.GetErrorActionResult(context.RouteData, HttpStatusCode.BadRequest, 201,
$"does not support the requested query. Please check the capabilities (t=caps) and make sure the search mode and categories are supported."); $"{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 manualResult = new ManualSearchResult();
var trackers = IndexerService.GetAllIndexers().ToList().Where(t => t.IsConfigured); var trackers = IndexerService.GetAllIndexers().ToList().Where(t => t.IsConfigured);
if (request.Tracker != null) if (request.Tracker != null)
{
trackers = trackers.Where(t => request.Tracker.Contains(t.Id)); trackers = trackers.Where(t => request.Tracker.Contains(t.Id));
}
trackers = trackers.Where(t => t.CanHandleQuery(CurrentQuery)); 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 try
{ {
var aggregateTask = Task.WhenAll(tasks); var aggregateTask = Task.WhenAll(tasks);

View File

@ -326,19 +326,30 @@ namespace Jackett.Test.Common.Models
} }
[Test] [Test]
public void TestSupportsCategories() public void TestSupportedCategories()
{ {
var tcc = CreateTestDataset(); var tcc = CreateTestDataset();
Assert.True(tcc.SupportsCategories(new []{ TorznabCatType.Movies.ID })); // parent cat Assert.AreEqual( new[] { TorznabCatType.Movies.ID }, // parent cat
Assert.True(tcc.SupportsCategories(new []{ TorznabCatType.MoviesSD.ID })); // child cat tcc.SupportedCategories(new []{ TorznabCatType.Movies.ID }));
Assert.True(tcc.SupportsCategories(new []{ TorznabCatType.Movies.ID, TorznabCatType.MoviesSD.ID })); // parent & child Assert.AreEqual( new[] { TorznabCatType.MoviesSD.ID }, // child cat
Assert.True(tcc.SupportsCategories(new []{ 100040 })); // custom cat tcc.SupportedCategories(new []{ TorznabCatType.MoviesSD.ID }));
Assert.False(tcc.SupportsCategories(new []{ TorznabCatType.Movies3D.ID })); // not supported child cat Assert.AreEqual( new[] { TorznabCatType.Movies.ID, TorznabCatType.MoviesSD.ID }, // parent & child cat
Assert.False(tcc.SupportsCategories(new []{ 9999 })); // unknown cat tcc.SupportedCategories(new []{ TorznabCatType.Movies.ID, TorznabCatType.MoviesSD.ID }));
Assert.False(tcc.SupportsCategories(new []{ 100001 })); // unknown custom cat Assert.AreEqual( new[] { 100040 }, // custom cat
Assert.False(tcc.SupportsCategories(new int[]{})); // empty list tcc.SupportedCategories(new []{ 100040 }));
Assert.False(tcc.SupportsCategories(null)); // null 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] [Test]