Co-Authored-By: Mark McDowall <markus101@users.noreply.github.com>pull/3011/head
parent
77041a5401
commit
a26cbdf61f
@ -0,0 +1,136 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using FizzWare.NBuilder;
|
||||||
|
using FluentAssertions;
|
||||||
|
using Moq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using NzbDrone.Core.Datastore;
|
||||||
|
using NzbDrone.Core.DecisionEngine.Specifications.RssSync;
|
||||||
|
using NzbDrone.Core.Indexers;
|
||||||
|
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||||
|
using NzbDrone.Core.Music;
|
||||||
|
using NzbDrone.Core.Parser.Model;
|
||||||
|
using NzbDrone.Core.Test.Framework;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Test.DecisionEngineTests.RssSync
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class IndexerTagSpecificationFixture : CoreTest<IndexerTagSpecification>
|
||||||
|
{
|
||||||
|
private IndexerTagSpecification _specification;
|
||||||
|
|
||||||
|
private RemoteAlbum _parseResultMulti;
|
||||||
|
private IndexerDefinition _fakeIndexerDefinition;
|
||||||
|
private Artist _fakeArtist;
|
||||||
|
private Album _firstAlbum;
|
||||||
|
private Album _secondAlbum;
|
||||||
|
private ReleaseInfo _fakeRelease;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void Setup()
|
||||||
|
{
|
||||||
|
_fakeIndexerDefinition = new IndexerDefinition
|
||||||
|
{
|
||||||
|
Tags = new HashSet<int>()
|
||||||
|
};
|
||||||
|
|
||||||
|
Mocker
|
||||||
|
.GetMock<IIndexerFactory>()
|
||||||
|
.Setup(m => m.Get(It.IsAny<int>()))
|
||||||
|
.Throws(new ModelNotFoundException(typeof(IndexerDefinition), -1));
|
||||||
|
|
||||||
|
Mocker
|
||||||
|
.GetMock<IIndexerFactory>()
|
||||||
|
.Setup(m => m.Get(1))
|
||||||
|
.Returns(_fakeIndexerDefinition);
|
||||||
|
|
||||||
|
_specification = Mocker.Resolve<IndexerTagSpecification>();
|
||||||
|
|
||||||
|
_fakeArtist = Builder<Artist>.CreateNew()
|
||||||
|
.With(c => c.Monitored = true)
|
||||||
|
.With(c => c.Tags = new HashSet<int>())
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
_fakeRelease = new ReleaseInfo
|
||||||
|
{
|
||||||
|
IndexerId = 1
|
||||||
|
};
|
||||||
|
|
||||||
|
_firstAlbum = new Album { Monitored = true };
|
||||||
|
_secondAlbum = new Album { Monitored = true };
|
||||||
|
|
||||||
|
var doubleEpisodeList = new List<Album> { _firstAlbum, _secondAlbum };
|
||||||
|
|
||||||
|
_parseResultMulti = new RemoteAlbum
|
||||||
|
{
|
||||||
|
Artist = _fakeArtist,
|
||||||
|
Albums = doubleEpisodeList,
|
||||||
|
Release = _fakeRelease
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void indexer_and_series_without_tags_should_return_true()
|
||||||
|
{
|
||||||
|
_fakeIndexerDefinition.Tags = new HashSet<int>();
|
||||||
|
_fakeArtist.Tags = new HashSet<int>();
|
||||||
|
|
||||||
|
_specification.IsSatisfiedBy(_parseResultMulti, new AlbumSearchCriteria { MonitoredEpisodesOnly = true }).Accepted.Should().BeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void indexer_with_tags_series_without_tags_should_return_false()
|
||||||
|
{
|
||||||
|
_fakeIndexerDefinition.Tags = new HashSet<int> { 123 };
|
||||||
|
_fakeArtist.Tags = new HashSet<int>();
|
||||||
|
|
||||||
|
_specification.IsSatisfiedBy(_parseResultMulti, new AlbumSearchCriteria { MonitoredEpisodesOnly = true }).Accepted.Should().BeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void indexer_without_tags_series_with_tags_should_return_true()
|
||||||
|
{
|
||||||
|
_fakeIndexerDefinition.Tags = new HashSet<int>();
|
||||||
|
_fakeArtist.Tags = new HashSet<int> { 123 };
|
||||||
|
|
||||||
|
_specification.IsSatisfiedBy(_parseResultMulti, new AlbumSearchCriteria { MonitoredEpisodesOnly = true }).Accepted.Should().BeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void indexer_with_tags_series_with_matching_tags_should_return_true()
|
||||||
|
{
|
||||||
|
_fakeIndexerDefinition.Tags = new HashSet<int> { 123, 456 };
|
||||||
|
_fakeArtist.Tags = new HashSet<int> { 123, 789 };
|
||||||
|
|
||||||
|
_specification.IsSatisfiedBy(_parseResultMulti, new AlbumSearchCriteria { MonitoredEpisodesOnly = true }).Accepted.Should().BeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void indexer_with_tags_series_with_different_tags_should_return_false()
|
||||||
|
{
|
||||||
|
_fakeIndexerDefinition.Tags = new HashSet<int> { 456 };
|
||||||
|
_fakeArtist.Tags = new HashSet<int> { 123, 789 };
|
||||||
|
|
||||||
|
_specification.IsSatisfiedBy(_parseResultMulti, new AlbumSearchCriteria { MonitoredEpisodesOnly = true }).Accepted.Should().BeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void release_without_indexerid_should_return_true()
|
||||||
|
{
|
||||||
|
_fakeIndexerDefinition.Tags = new HashSet<int> { 456 };
|
||||||
|
_fakeArtist.Tags = new HashSet<int> { 123, 789 };
|
||||||
|
_fakeRelease.IndexerId = 0;
|
||||||
|
|
||||||
|
_specification.IsSatisfiedBy(_parseResultMulti, new AlbumSearchCriteria { MonitoredEpisodesOnly = true }).Accepted.Should().BeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void release_with_invalid_indexerid_should_return_true()
|
||||||
|
{
|
||||||
|
_fakeIndexerDefinition.Tags = new HashSet<int> { 456 };
|
||||||
|
_fakeArtist.Tags = new HashSet<int> { 123, 789 };
|
||||||
|
_fakeRelease.IndexerId = 2;
|
||||||
|
|
||||||
|
_specification.IsSatisfiedBy(_parseResultMulti, new AlbumSearchCriteria { MonitoredEpisodesOnly = true }).Accepted.Should().BeTrue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
using FluentMigrator;
|
||||||
|
using NzbDrone.Core.Datastore.Migration.Framework;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Datastore.Migration
|
||||||
|
{
|
||||||
|
[Migration(059)]
|
||||||
|
public class add_indexer_tags : NzbDroneMigrationBase
|
||||||
|
{
|
||||||
|
protected override void MainDbUpgrade()
|
||||||
|
{
|
||||||
|
Execute.Sql("DELETE FROM Indexers WHERE Implementation = 'Omgwtfnzbs'");
|
||||||
|
Execute.Sql("DELETE FROM Indexers WHERE Implementation = 'Waffles'");
|
||||||
|
|
||||||
|
Alter.Table("Indexers").AddColumn("Tags").AsString().Nullable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,56 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using NLog;
|
||||||
|
using NzbDrone.Common.Extensions;
|
||||||
|
using NzbDrone.Core.Datastore;
|
||||||
|
using NzbDrone.Core.Indexers;
|
||||||
|
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||||
|
using NzbDrone.Core.Parser.Model;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
|
||||||
|
{
|
||||||
|
public class IndexerTagSpecification : IDecisionEngineSpecification
|
||||||
|
{
|
||||||
|
private readonly Logger _logger;
|
||||||
|
private readonly IIndexerFactory _indexerFactory;
|
||||||
|
|
||||||
|
public IndexerTagSpecification(Logger logger, IIndexerFactory indexerFactory)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_indexerFactory = indexerFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SpecificationPriority Priority => SpecificationPriority.Default;
|
||||||
|
public RejectionType Type => RejectionType.Permanent;
|
||||||
|
|
||||||
|
public virtual Decision IsSatisfiedBy(RemoteAlbum subject, SearchCriteriaBase searchCriteria)
|
||||||
|
{
|
||||||
|
if (subject.Release == null || subject.Artist?.Tags == null || subject.Release.IndexerId == 0)
|
||||||
|
{
|
||||||
|
return Decision.Accept();
|
||||||
|
}
|
||||||
|
|
||||||
|
IndexerDefinition indexer;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
indexer = _indexerFactory.Get(subject.Release.IndexerId);
|
||||||
|
}
|
||||||
|
catch (ModelNotFoundException)
|
||||||
|
{
|
||||||
|
_logger.Debug("Indexer with id {0} does not exist, skipping indexer tags check", subject.Release.IndexerId);
|
||||||
|
return Decision.Accept();
|
||||||
|
}
|
||||||
|
|
||||||
|
// If indexer has tags, check that at least one of them is present on the series
|
||||||
|
var indexerTags = indexer.Tags;
|
||||||
|
|
||||||
|
if (indexerTags.Any() && indexerTags.Intersect(subject.Artist.Tags).Empty())
|
||||||
|
{
|
||||||
|
_logger.Debug("Indexer {0} has tags. None of these are present on artist {1}. Rejecting", subject.Release.Indexer, subject.Artist);
|
||||||
|
|
||||||
|
return Decision.Reject("Artist tags do not match any of the indexer tags");
|
||||||
|
}
|
||||||
|
|
||||||
|
return Decision.Accept();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,65 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using System.Text;
|
|
||||||
using NzbDrone.Common.Extensions;
|
|
||||||
using NzbDrone.Common.Http;
|
|
||||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
|
||||||
|
|
||||||
namespace NzbDrone.Core.Indexers.Omgwtfnzbs
|
|
||||||
{
|
|
||||||
public class OmgwtfnzbsRequestGenerator : IIndexerRequestGenerator
|
|
||||||
{
|
|
||||||
public string BaseUrl { get; set; }
|
|
||||||
public OmgwtfnzbsSettings Settings { get; set; }
|
|
||||||
|
|
||||||
public OmgwtfnzbsRequestGenerator()
|
|
||||||
{
|
|
||||||
BaseUrl = "https://rss.omgwtfnzbs.me/rss-download.php";
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual IndexerPageableRequestChain GetRecentRequests()
|
|
||||||
{
|
|
||||||
var pageableRequests = new IndexerPageableRequestChain();
|
|
||||||
|
|
||||||
pageableRequests.Add(GetPagedRequests(null));
|
|
||||||
|
|
||||||
return pageableRequests;
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual IndexerPageableRequestChain GetSearchRequests(AlbumSearchCriteria searchCriteria)
|
|
||||||
{
|
|
||||||
var pageableRequests = new IndexerPageableRequestChain();
|
|
||||||
|
|
||||||
pageableRequests.Add(GetPagedRequests(string.Format("{0}+{1}",
|
|
||||||
searchCriteria.ArtistQuery,
|
|
||||||
searchCriteria.AlbumQuery)));
|
|
||||||
|
|
||||||
return pageableRequests;
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual IndexerPageableRequestChain GetSearchRequests(ArtistSearchCriteria searchCriteria)
|
|
||||||
{
|
|
||||||
var pageableRequests = new IndexerPageableRequestChain();
|
|
||||||
|
|
||||||
pageableRequests.Add(GetPagedRequests(string.Format("{0}",
|
|
||||||
searchCriteria.ArtistQuery)));
|
|
||||||
|
|
||||||
return pageableRequests;
|
|
||||||
}
|
|
||||||
|
|
||||||
private IEnumerable<IndexerRequest> GetPagedRequests(string query)
|
|
||||||
{
|
|
||||||
var url = new StringBuilder();
|
|
||||||
|
|
||||||
// Category 22 is Music-FLAC, category 7 is Music-MP3
|
|
||||||
url.AppendFormat("{0}?catid=22,7&user={1}&api={2}&eng=1&delay={3}", BaseUrl, Settings.Username, Settings.ApiKey, Settings.Delay);
|
|
||||||
|
|
||||||
if (query.IsNotNullOrWhiteSpace())
|
|
||||||
{
|
|
||||||
url = url.Replace("rss-download.php", "rss-search.php");
|
|
||||||
url.AppendFormat("&search={0}", query);
|
|
||||||
}
|
|
||||||
|
|
||||||
yield return new IndexerRequest(url.ToString(), HttpAccept.Rss);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,46 +0,0 @@
|
|||||||
using FluentValidation;
|
|
||||||
using NzbDrone.Core.Annotations;
|
|
||||||
using NzbDrone.Core.Validation;
|
|
||||||
|
|
||||||
namespace NzbDrone.Core.Indexers.Omgwtfnzbs
|
|
||||||
{
|
|
||||||
public class OmgwtfnzbsSettingsValidator : AbstractValidator<OmgwtfnzbsSettings>
|
|
||||||
{
|
|
||||||
public OmgwtfnzbsSettingsValidator()
|
|
||||||
{
|
|
||||||
RuleFor(c => c.Username).NotEmpty();
|
|
||||||
RuleFor(c => c.ApiKey).NotEmpty();
|
|
||||||
RuleFor(c => c.Delay).GreaterThanOrEqualTo(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class OmgwtfnzbsSettings : IIndexerSettings
|
|
||||||
{
|
|
||||||
private static readonly OmgwtfnzbsSettingsValidator Validator = new OmgwtfnzbsSettingsValidator();
|
|
||||||
|
|
||||||
public OmgwtfnzbsSettings()
|
|
||||||
{
|
|
||||||
Delay = 30;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unused since Omg has a hardcoded url.
|
|
||||||
public string BaseUrl { get; set; }
|
|
||||||
|
|
||||||
[FieldDefinition(0, Label = "Username", Privacy = PrivacyLevel.UserName)]
|
|
||||||
public string Username { get; set; }
|
|
||||||
|
|
||||||
[FieldDefinition(1, Label = "API Key", Privacy = PrivacyLevel.ApiKey)]
|
|
||||||
public string ApiKey { get; set; }
|
|
||||||
|
|
||||||
[FieldDefinition(2, Label = "Delay", HelpText = "Time in minutes to delay new nzbs before they appear on the RSS feed", Advanced = true)]
|
|
||||||
public int Delay { get; set; }
|
|
||||||
|
|
||||||
[FieldDefinition(3, Type = FieldType.Number, Label = "Early Download Limit", Unit = "days", HelpText = "Time before release date Lidarr will download from this indexer, empty is no limit", Advanced = true)]
|
|
||||||
public int? EarlyReleaseLimit { get; set; }
|
|
||||||
|
|
||||||
public NzbDroneValidationResult Validate()
|
|
||||||
{
|
|
||||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,63 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using System.Text;
|
|
||||||
using NzbDrone.Common.Extensions;
|
|
||||||
using NzbDrone.Common.Http;
|
|
||||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
|
||||||
|
|
||||||
namespace NzbDrone.Core.Indexers.Waffles
|
|
||||||
{
|
|
||||||
public class WafflesRequestGenerator : IIndexerRequestGenerator
|
|
||||||
{
|
|
||||||
public WafflesSettings Settings { get; set; }
|
|
||||||
public int MaxPages { get; set; }
|
|
||||||
|
|
||||||
public WafflesRequestGenerator()
|
|
||||||
{
|
|
||||||
MaxPages = 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual IndexerPageableRequestChain GetRecentRequests()
|
|
||||||
{
|
|
||||||
var pageableRequests = new IndexerPageableRequestChain();
|
|
||||||
|
|
||||||
pageableRequests.Add(GetPagedRequests(MaxPages, null));
|
|
||||||
|
|
||||||
return pageableRequests;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IndexerPageableRequestChain GetSearchRequests(AlbumSearchCriteria searchCriteria)
|
|
||||||
{
|
|
||||||
var pageableRequests = new IndexerPageableRequestChain();
|
|
||||||
|
|
||||||
pageableRequests.Add(GetPagedRequests(MaxPages, string.Format("&q=artist:{0} album:{1}", searchCriteria.ArtistQuery, searchCriteria.AlbumQuery)));
|
|
||||||
|
|
||||||
return pageableRequests;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IndexerPageableRequestChain GetSearchRequests(ArtistSearchCriteria searchCriteria)
|
|
||||||
{
|
|
||||||
var pageableRequests = new IndexerPageableRequestChain();
|
|
||||||
|
|
||||||
pageableRequests.Add(GetPagedRequests(MaxPages, string.Format("&q=artist:{0}", searchCriteria.ArtistQuery)));
|
|
||||||
|
|
||||||
return pageableRequests;
|
|
||||||
}
|
|
||||||
|
|
||||||
private IEnumerable<IndexerRequest> GetPagedRequests(int maxPages, string query)
|
|
||||||
{
|
|
||||||
var url = new StringBuilder();
|
|
||||||
|
|
||||||
url.AppendFormat("{0}/browse.php?rss=1&c0=1&uid={1}&passkey={2}", Settings.BaseUrl.Trim().TrimEnd('/'), Settings.UserId, Settings.RssPasskey);
|
|
||||||
|
|
||||||
if (query.IsNotNullOrWhiteSpace())
|
|
||||||
{
|
|
||||||
url.AppendFormat(query);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var page = 0; page < maxPages; page++)
|
|
||||||
{
|
|
||||||
yield return new IndexerRequest(string.Format("{0}&p={1}", url, page), HttpAccept.Rss);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,50 +0,0 @@
|
|||||||
using FluentValidation;
|
|
||||||
using NzbDrone.Core.Annotations;
|
|
||||||
using NzbDrone.Core.Validation;
|
|
||||||
|
|
||||||
namespace NzbDrone.Core.Indexers.Waffles
|
|
||||||
{
|
|
||||||
public class WafflesSettingsValidator : AbstractValidator<WafflesSettings>
|
|
||||||
{
|
|
||||||
public WafflesSettingsValidator()
|
|
||||||
{
|
|
||||||
RuleFor(c => c.BaseUrl).ValidRootUrl();
|
|
||||||
RuleFor(c => c.UserId).NotEmpty();
|
|
||||||
RuleFor(c => c.RssPasskey).NotEmpty();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class WafflesSettings : ITorrentIndexerSettings
|
|
||||||
{
|
|
||||||
private static readonly WafflesSettingsValidator Validator = new WafflesSettingsValidator();
|
|
||||||
|
|
||||||
public WafflesSettings()
|
|
||||||
{
|
|
||||||
BaseUrl = "https://www.waffles.ch";
|
|
||||||
MinimumSeeders = IndexerDefaults.MINIMUM_SEEDERS;
|
|
||||||
}
|
|
||||||
|
|
||||||
[FieldDefinition(0, Label = "Website URL")]
|
|
||||||
public string BaseUrl { get; set; }
|
|
||||||
|
|
||||||
[FieldDefinition(1, Label = "UserId", Privacy = PrivacyLevel.UserName)]
|
|
||||||
public string UserId { get; set; }
|
|
||||||
|
|
||||||
[FieldDefinition(2, Label = "RSS Passkey", Privacy = PrivacyLevel.ApiKey)]
|
|
||||||
public string RssPasskey { get; set; }
|
|
||||||
|
|
||||||
[FieldDefinition(3, Type = FieldType.Textbox, Label = "Minimum Seeders", HelpText = "Minimum number of seeders required.", Advanced = true)]
|
|
||||||
public int MinimumSeeders { get; set; }
|
|
||||||
|
|
||||||
[FieldDefinition(4)]
|
|
||||||
public SeedCriteriaSettings SeedCriteria { get; set; } = new SeedCriteriaSettings();
|
|
||||||
|
|
||||||
[FieldDefinition(5, Type = FieldType.Number, Label = "Early Download Limit", Unit = "days", HelpText = "Time before release date Lidarr will download from this indexer, empty is no limit", Advanced = true)]
|
|
||||||
public int? EarlyReleaseLimit { get; set; }
|
|
||||||
|
|
||||||
public NzbDroneValidationResult Validate()
|
|
||||||
{
|
|
||||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in new issue