Fixed: Processing of mixed newznab/torznab api such as the experimental animetosho api.

Ref #1384
Taloth Saldono 8 years ago
parent a41b5723d4
commit 4fbc481780

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:newznab="http://www.newznab.com/DTD/2010/feeds/attributes/" xmlns:torznab="http://torznab.com/schemas/2015/feed">
<channel>
<atom:link href="https://localhost/feed/rssx" rel="self" type="application/rss+xml" />
<title>Anime Tosho</title>
<link>https://localhost/</link>
<description>Latest releases feed</description>
<language>en-gb</language>
<ttl>30</ttl>
<lastBuildDate>Wed, 17 May 2017 20:36:06 +0000</lastBuildDate>
<newznab:response offset="0"/>
<item>
<title>[finFAGs]_Frame_Arms_Girl_07_(1280x720_TV_AAC)_[1262B6F7].mkv</title>
<pubDate>Wed, 17 May 2017 20:36:06 +0000</pubDate>
<guid isPermaLink="true">https://localhost/view/123451</guid>
<category>Anime</category>
<description><![CDATA[<strong>Total Size</strong>: 301.8 MB<br />]]></description>
<link>https://localhost/view/finfags-_frame_arms_girl_07_-1280x720_tv_aac-_-1262b6f7-mkv.123451</link>
<comments>https://localhost/view/finfags-_frame_arms_girl_07_-1280x720_tv_aac-_-1262b6f7-mkv.123451</comments>
<enclosure url="http://storage.localhost/torrents/123451.torrent" type="application/x-bittorrent" length="0" />
<source url="http://www.tokyotosho.info/details.php?id=123451">TokyoTosho</source>
<newznab:attr name="category" value="5070" />
<newznab:attr name="category" value="100001" />
<newznab:attr name="files" value="1" />
<newznab:attr name="size" value="316477946" />
<torznab:attr name="files" value="1" />
<torznab:attr name="size" value="316477946" />
<torznab:attr name="category" value="5070" />
<torznab:attr name="category" value="100001" />
<torznab:attr name="infohash" value="2d69a861bef5a9f2cdf791b7328e37b7953205e1" />
<torznab:attr name="magneturl" value="magnet:?xt=urn:btih:VU2QYN66WU7FTPXSG3TFDRXW6KTEBPBF" />
</item>
<item>
<title>[HorribleSubs] Frame Arms Girl - 07 [720p].mkv</title>
<pubDate>Mon, 15 May 2017 19:15:56 +0000</pubDate>
<guid isPermaLink="true">https://localhost/view/123452</guid>
<category>Anime</category>
<description><![CDATA[<strong>Total Size</strong>: 452.0 MB<br />]]></description>
<link>https://localhost/view/horriblesubs-frame-arms-girl-07-720p-mkv.123452</link>
<comments>https://localhost/view/horriblesubs-frame-arms-girl-07-720p-mkv.123452</comments>
<enclosure url="http://storage.localhost/torrents/123452.torrent" type="application/x-bittorrent" length="0" />
<enclosure url="http://storage.localhost/nzb/123452.nzb" type="application/x-nzb" length="0" />
<source url="http://www.tokyotosho.info/details.php?id=123452">TokyoTosho</source>
<newznab:attr name="category" value="5070" />
<newznab:attr name="category" value="100001" />
<newznab:attr name="files" value="1" />
<newznab:attr name="size" value="473987489" />
<torznab:attr name="files" value="1" />
<torznab:attr name="size" value="473987489" />
<torznab:attr name="category" value="5070" />
<torznab:attr name="category" value="100001" />
<torznab:attr name="infohash" value="bff4afebcd50c21949ed6a06323d2120c649bd82" />
<torznab:attr name="magneturl" value="magnet:?xt=urn:btih:5QK77JL7LZVIMEGKJ5VVAMMR5EEQMMSN" />
</item>
</channel>
</rss>

@ -7,6 +7,7 @@ using NUnit.Framework;
using NzbDrone.Common.Http;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Indexers.Newznab;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Test.Common;
@ -63,6 +64,35 @@ namespace NzbDrone.Core.Test.IndexerTests.NewznabTests
releaseInfo.Size.Should().Be(1183105773);
}
[Test]
public void should_parse_recent_feed_from_newznab_animetosho()
{
var recentFeed = ReadAllText(@"Files/Indexers/Torznab/torznab_animetosho.xml");
Mocker.GetMock<IHttpClient>()
.Setup(o => o.Execute(It.Is<HttpRequest>(v => v.Method == HttpMethod.GET)))
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), recentFeed));
var releases = Subject.FetchRecent();
releases.Should().HaveCount(1);
releases.First().Should().BeOfType<ReleaseInfo>();
var releaseInfo = releases.First() as ReleaseInfo;
releaseInfo.Title.Should().Be("[HorribleSubs] Frame Arms Girl - 07 [720p].mkv");
releaseInfo.DownloadProtocol.Should().Be(DownloadProtocol.Usenet);
releaseInfo.DownloadUrl.Should().Be("http://storage.localhost/nzb/123452.nzb");
releaseInfo.InfoUrl.Should().Be("https://localhost/view/horriblesubs-frame-arms-girl-07-720p-mkv.123452");
releaseInfo.CommentUrl.Should().Be("https://localhost/view/horriblesubs-frame-arms-girl-07-720p-mkv.123452");
releaseInfo.Indexer.Should().Be(Subject.Definition.Name);
releaseInfo.PublishDate.Should().Be(DateTime.Parse("Mon, 15 May 2017 19:15:56 +0000").ToUniversalTime());
releaseInfo.Size.Should().Be(473987489);
releaseInfo.TvdbId.Should().Be(0);
releaseInfo.TvRageId.Should().Be(0);
}
[Test]
public void should_use_pagesize_reported_by_caps()
{

@ -256,11 +256,11 @@ namespace NzbDrone.Core.Test.IndexerTests.TorrentRssIndexerTests
[TestCase("BitMeTv/BitMeTv.xml")]
[TestCase("Fanzub/fanzub.xml")]
[TestCase("IPTorrents/IPTorrents.xml")]
[TestCase("Newznab/newznab_nzb_su.xml")]
[TestCase("Nyaa/Nyaa.xml")]
[TestCase("Omgwtfnzbs/Omgwtfnzbs.xml")]
[TestCase("Torznab/torznab_hdaccess_net.xml")]
[TestCase("Torznab/torznab_tpb.xml")]
[TestCase("Torznab/torznab_animetosho.xml")]
public void should_detect_recent_feed(string rssXmlFile)
{
GivenRecentFeedResponse(rssXmlFile);

@ -97,6 +97,37 @@ namespace NzbDrone.Core.Test.IndexerTests.TorznabTests
releaseInfo.Peers.Should().Be(36724);
}
[Test]
public void should_parse_recent_feed_from_torznab_animetosho()
{
var recentFeed = ReadAllText(@"Files/Indexers/Torznab/torznab_animetosho.xml");
Mocker.GetMock<IHttpClient>()
.Setup(o => o.Execute(It.Is<HttpRequest>(v => v.Method == HttpMethod.GET)))
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), recentFeed));
var releases = Subject.FetchRecent();
releases.Should().HaveCount(2);
releases.First().Should().BeOfType<TorrentInfo>();
var releaseInfo = releases.First() as TorrentInfo;
releaseInfo.Title.Should().Be("[finFAGs]_Frame_Arms_Girl_07_(1280x720_TV_AAC)_[1262B6F7].mkv");
releaseInfo.DownloadProtocol.Should().Be(DownloadProtocol.Torrent);
releaseInfo.DownloadUrl.Should().Be("http://storage.localhost/torrents/123451.torrent");
releaseInfo.InfoUrl.Should().Be("https://localhost/view/finfags-_frame_arms_girl_07_-1280x720_tv_aac-_-1262b6f7-mkv.123451");
releaseInfo.CommentUrl.Should().Be("https://localhost/view/finfags-_frame_arms_girl_07_-1280x720_tv_aac-_-1262b6f7-mkv.123451");
releaseInfo.Indexer.Should().Be(Subject.Definition.Name);
releaseInfo.PublishDate.Should().Be(DateTime.Parse("Wed, 17 May 2017 20:36:06 +0000").ToUniversalTime());
releaseInfo.Size.Should().Be(316477946);
releaseInfo.TvdbId.Should().Be(0);
releaseInfo.TvRageId.Should().Be(0);
releaseInfo.InfoHash.Should().Be("2d69a861bef5a9f2cdf791b7328e37b7953205e1");
releaseInfo.Seeders.Should().BeNull();
releaseInfo.Peers.Should().BeNull();
}
[Test]
public void should_use_pagesize_reported_by_caps()
{

@ -433,6 +433,9 @@
<Content Include="Files\Indexers\TorrentRss\LimeTorrents.xml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="Files\Indexers\Torznab\torznab_animetosho.xml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="License.txt" />
<None Include="Files\Indexers\BroadcastheNet\RecentFeed.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>

@ -166,7 +166,7 @@ namespace NzbDrone.Core.Indexers
}
}
releases.AddRange(pagedReleases);
releases.AddRange(pagedReleases.Where(IsValidRelease));
}
if (releases.Any())
@ -268,6 +268,16 @@ namespace NzbDrone.Core.Indexers
return CleanupReleases(releases);
}
protected virtual bool IsValidRelease(ReleaseInfo release)
{
if (release.DownloadUrl.IsNullOrWhiteSpace())
{
return false;
}
return true;
}
protected virtual bool IsFullPage(IList<ReleaseInfo> page)
{
return PageSize != 0 && page.Count >= PageSize;

@ -74,6 +74,7 @@ namespace NzbDrone.Core.Indexers
result.ForEach(c =>
{
c.Guid = string.Concat(Definition.Id, "_", c.Guid);
c.IndexerId = Definition.Id;
c.Indexer = Definition.Name;
c.DownloadProtocol = Protocol;

@ -41,7 +41,7 @@ namespace NzbDrone.Core.Indexers.Newznab
{
var capabilities = new NewznabCapabilities();
var url = string.Format("{0}/api?t=caps", indexerSettings.BaseUrl.TrimEnd('/'));
var url = string.Format("{0}{1}?t=caps", indexerSettings.BaseUrl.TrimEnd('/'), indexerSettings.ApiPath.TrimEnd('/'));
if (indexerSettings.ApiKey.IsNotNullOrWhiteSpace())
{

@ -104,6 +104,10 @@ namespace NzbDrone.Core.Indexers.Newznab
{
pageableRequests.Add(GetPagedRequests(MaxPages, Settings.Categories.Concat(Settings.AnimeCategories), "tvsearch", ""));
}
else if (capabilities.SupportedSearchParameters != null)
{
pageableRequests.Add(GetPagedRequests(MaxPages, Settings.AnimeCategories, "search", ""));
}
return pageableRequests;
}
@ -249,7 +253,7 @@ namespace NzbDrone.Core.Indexers.Newznab
var categoriesQuery = string.Join(",", categories.Distinct());
var baseUrl = string.Format("{0}/api?t={1}&cat={2}&extended=1{3}", Settings.BaseUrl.TrimEnd('/'), searchType, categoriesQuery, Settings.AdditionalParameters);
var baseUrl = string.Format("{0}{1}?t={2}&cat={3}&extended=1{4}", Settings.BaseUrl.TrimEnd('/'), Settings.ApiPath.TrimEnd('/'), searchType, categoriesQuery, Settings.AdditionalParameters);
if (Settings.ApiKey.IsNotNullOrWhiteSpace())
{

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using NzbDrone.Common.Extensions;
@ -13,7 +14,8 @@ namespace NzbDrone.Core.Indexers.Newznab
public NewznabRssParser()
{
PreferredEnclosureMimeType = "application/x-nzb";
PreferredEnclosureMimeTypes = UsenetEnclosureMimeTypes;
UseEnclosureUrl = true;
}
protected override bool PreProcess(IndexerResponse indexerResponse)
@ -45,6 +47,24 @@ namespace NzbDrone.Core.Indexers.Newznab
throw new NewznabException(indexerResponse, errorMessage);
}
protected override bool PostProcess(IndexerResponse indexerResponse, List<XElement> items, List<ReleaseInfo> releases)
{
var enclosureTypes = items.SelectMany(GetEnclosures).Select(v => v.Type).Distinct().ToArray();
if (enclosureTypes.Any() && enclosureTypes.Intersect(PreferredEnclosureMimeTypes).Empty())
{
if (enclosureTypes.Intersect(TorrentEnclosureMimeTypes).Any())
{
_logger.Warn("Feed does not contain {0}, found {1}, did you intend to add a Torznab indexer?", NzbEnclosureMimeType, enclosureTypes[0]);
}
else
{
_logger.Warn("Feed does not contain {0}, found {1}.", NzbEnclosureMimeType, enclosureTypes[0]);
}
}
return true;
}
protected override ReleaseInfo ProcessItem(XElement item, ReleaseInfo releaseInfo)
{
releaseInfo = base.ProcessItem(item, releaseInfo);
@ -55,17 +75,6 @@ namespace NzbDrone.Core.Indexers.Newznab
return releaseInfo;
}
protected override ReleaseInfo PostProcess(XElement item, ReleaseInfo releaseInfo)
{
var enclosureType = GetEnclosure(item).Attribute("type").Value;
if (enclosureType.Contains("application/x-bittorrent"))
{
throw new UnsupportedFeedException("Feed contains {0}, did you intend to add a Torznab indexer?", enclosureType);
}
return base.PostProcess(item, releaseInfo);
}
protected override string GetInfoUrl(XElement item)
{
return ParseUrl(item.TryGetValue("comments").TrimEnd("#comments"));
@ -102,18 +111,6 @@ namespace NzbDrone.Core.Indexers.Newznab
return base.GetPublishDate(item);
}
protected override string GetDownloadUrl(XElement item)
{
var url = base.GetDownloadUrl(item);
if (!Uri.IsWellFormedUriString(url, UriKind.Absolute))
{
url = ParseUrl((string)item.Element("enclosure").Attribute("url"));
}
return url;
}
protected virtual int GetTvdbId(XElement item)
{
var tvdbIdString = TryGetNewznabAttribute(item, "tvdbid");

@ -48,6 +48,7 @@ namespace NzbDrone.Core.Indexers.Newznab
});
RuleFor(c => c.BaseUrl).ValidRootUrl();
RuleFor(c => c.ApiPath).ValidUrlBase("/api");
RuleFor(c => c.ApiKey).NotEmpty().When(ShouldHaveApiKey);
RuleFor(c => c.AdditionalParameters).Matches(AdditionalParametersRegex)
.When(c => !c.AdditionalParameters.IsNullOrWhiteSpace());
@ -60,6 +61,7 @@ namespace NzbDrone.Core.Indexers.Newznab
public NewznabSettings()
{
ApiPath = "/api";
Categories = new[] { 5030, 5040 };
AnimeCategories = Enumerable.Empty<int>();
}
@ -67,19 +69,22 @@ namespace NzbDrone.Core.Indexers.Newznab
[FieldDefinition(0, Label = "URL")]
public string BaseUrl { get; set; }
[FieldDefinition(1, Label = "API Key")]
[FieldDefinition(1, Label = "API Path", HelpText = "Path to the api, usually /api", Advanced = true)]
public string ApiPath { get; set; }
[FieldDefinition(2, Label = "API Key")]
public string ApiKey { get; set; }
[FieldDefinition(2, Label = "Categories", HelpText = "Comma Separated list, leave blank to disable standard/daily shows", Advanced = true)]
[FieldDefinition(3, Label = "Categories", HelpText = "Comma Separated list, leave blank to disable standard/daily shows")]
public IEnumerable<int> Categories { get; set; }
[FieldDefinition(3, Label = "Anime Categories", HelpText = "Comma Separated list, leave blank to disable anime", Advanced = true)]
[FieldDefinition(4, Label = "Anime Categories", HelpText = "Comma Separated list, leave blank to disable anime")]
public IEnumerable<int> AnimeCategories { get; set; }
[FieldDefinition(4, Label = "Additional Parameters", HelpText = "Additional Newznab parameters", Advanced = true)]
[FieldDefinition(5, Label = "Additional Parameters", HelpText = "Additional Newznab parameters", Advanced = true)]
public string AdditionalParameters { get; set; }
// Field 5 is used by TorznabSettings MinimumSeeders
// Field 6 is used by TorznabSettings MinimumSeeders
// If you need to add another field here, update TorznabSettings as well and this comment
public virtual NzbDroneValidationResult Validate()

@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace NzbDrone.Core.Indexers
{
public class RssEnclosure
{
public string Url { get; set; }
public string Type { get; set; }
public long Length { get; set; }
}
}

@ -19,6 +19,11 @@ namespace NzbDrone.Core.Indexers
public class RssParser : IParseIndexerResponse
{
private static readonly Regex ReplaceEntities = new Regex("&[a-z]+;", RegexOptions.Compiled | RegexOptions.IgnoreCase);
public const string NzbEnclosureMimeType = "application/x-nzb";
public const string TorrentEnclosureMimeType = "application/x-bittorrent";
public const string MagnetEnclosureMimeType = "application/x-bittorrent;x-scheme-handler/magnet";
public static readonly string[] UsenetEnclosureMimeTypes = new[] { NzbEnclosureMimeType };
public static readonly string[] TorrentEnclosureMimeTypes = new[] { TorrentEnclosureMimeType, MagnetEnclosureMimeType };
protected readonly Logger _logger;
@ -32,7 +37,7 @@ namespace NzbDrone.Core.Indexers
// Parse "Size: 1.3 GB" or "1.3 GB" parts in the description element and use that as Size.
public bool ParseSizeInDescription { get; set; }
public string PreferredEnclosureMimeType { get; set; }
public string[] PreferredEnclosureMimeTypes { get; set; }
private IndexerResponse _indexerResponse;
@ -53,7 +58,7 @@ namespace NzbDrone.Core.Indexers
}
var document = LoadXmlDocument(indexerResponse);
var items = GetItems(document);
var items = GetItems(document).ToList();
foreach (var item in items)
{
@ -77,6 +82,11 @@ namespace NzbDrone.Core.Indexers
}
}
if (!PostProcess(indexerResponse, items, releases))
{
return new List<ReleaseInfo>();
}
return releases;
}
@ -124,6 +134,11 @@ namespace NzbDrone.Core.Indexers
return true;
}
protected virtual bool PostProcess(IndexerResponse indexerResponse, List<XElement> elements, List<ReleaseInfo> releases)
{
return true;
}
protected ReleaseInfo ProcessItem(XElement item)
{
var releaseInfo = CreateNewReleaseInfo();
@ -132,7 +147,7 @@ namespace NzbDrone.Core.Indexers
_logger.Trace("Parsed: {0}", releaseInfo.Title);
return PostProcess(item, releaseInfo);
return PostProcessItem(item, releaseInfo);
}
protected virtual ReleaseInfo ProcessItem(XElement item, ReleaseInfo releaseInfo)
@ -156,7 +171,7 @@ namespace NzbDrone.Core.Indexers
return releaseInfo;
}
protected virtual ReleaseInfo PostProcess(XElement item, ReleaseInfo releaseInfo)
protected virtual ReleaseInfo PostProcessItem(XElement item, ReleaseInfo releaseInfo)
{
return releaseInfo;
}
@ -187,7 +202,8 @@ namespace NzbDrone.Core.Indexers
{
if (UseEnclosureUrl)
{
return ParseUrl((string)GetEnclosure(item).Attribute("url"));
var enclosure = GetEnclosure(item);
return enclosure != null ? ParseUrl(enclosure.Url) : null;
}
return ParseUrl((string)item.Element("link"));
@ -228,29 +244,45 @@ namespace NzbDrone.Core.Indexers
if (enclosure != null)
{
return (long)enclosure.Attribute("length");
return enclosure.Length;
}
return 0;
}
protected virtual XElement GetEnclosure(XElement item)
protected virtual RssEnclosure[] GetEnclosures(XElement item)
{
var enclosures = item.Elements("enclosure")
.Select(v => new RssEnclosure
{
var enclosures = item.Elements("enclosure").ToArray();
Url = v.Attribute("url").Value,
Type = v.Attribute("type").Value,
Length = (long)v.Attribute("length")
})
.ToArray();
if (enclosures.Length == 0)
return enclosures;
}
protected RssEnclosure GetEnclosure(XElement item, bool enforceMimeType = true)
{
return null;
var enclosures = GetEnclosures(item);
return GetEnclosure(enclosures, enforceMimeType);
}
if (enclosures.Length == 1)
protected virtual RssEnclosure GetEnclosure(RssEnclosure[] enclosures, bool enforceMimeType = true)
{
if (enclosures.Length == 0)
{
return enclosures.First();
return null;
}
if (PreferredEnclosureMimeType != null)
if (PreferredEnclosureMimeTypes != null)
{
foreach (var preferredEnclosureType in PreferredEnclosureMimeTypes)
{
var preferredEnclosure = enclosures.FirstOrDefault(v => v.Attribute("type").Value == PreferredEnclosureMimeType);
var preferredEnclosure = enclosures.FirstOrDefault(v => v.Type == preferredEnclosureType);
if (preferredEnclosure != null)
{
@ -258,7 +290,13 @@ namespace NzbDrone.Core.Indexers
}
}
return item.Elements("enclosure").SingleOrDefault();
if (enforceMimeType)
{
return null;
}
}
return enclosures.SingleOrDefault();
}
protected IEnumerable<XElement> GetItems(XDocument document)

@ -16,7 +16,7 @@ namespace NzbDrone.Core.Indexers
public TorrentRssParser()
{
PreferredEnclosureMimeType = "application/x-bittorrent";
PreferredEnclosureMimeTypes = TorrentEnclosureMimeTypes;
}
public IEnumerable<XElement> GetItems(IndexerResponse indexerResponse)

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using NzbDrone.Common.Extensions;
@ -11,6 +12,11 @@ namespace NzbDrone.Core.Indexers.Torznab
{
public const string ns = "{http://torznab.com/schemas/2015/feed}";
public TorznabRssParser()
{
UseEnclosureUrl = true;
}
protected override bool PreProcess(IndexerResponse indexerResponse)
{
var xdoc = LoadXmlDocument(indexerResponse);
@ -36,6 +42,24 @@ namespace NzbDrone.Core.Indexers.Torznab
throw new TorznabException("Torznab error detected: {0}", errorMessage);
}
protected override bool PostProcess(IndexerResponse indexerResponse, List<XElement> items, List<ReleaseInfo> releases)
{
var enclosureTypes = items.SelectMany(GetEnclosures).Select(v => v.Type).Distinct().ToArray();
if (enclosureTypes.Any() && enclosureTypes.Intersect(PreferredEnclosureMimeTypes).Empty())
{
if (enclosureTypes.Intersect(UsenetEnclosureMimeTypes).Any())
{
_logger.Warn("Feed does not contain {0}, found {1}, did you intend to add a Newznab indexer?", TorrentEnclosureMimeType, enclosureTypes[0]);
}
else
{
_logger.Warn("Feed does not contain {0}, found {1}.", TorrentEnclosureMimeType, enclosureTypes[0]);
}
}
return true;
}
protected override ReleaseInfo ProcessItem(XElement item, ReleaseInfo releaseInfo)
{
var torrentInfo = base.ProcessItem(item, releaseInfo) as TorrentInfo;
@ -46,18 +70,6 @@ namespace NzbDrone.Core.Indexers.Torznab
return torrentInfo;
}
protected override ReleaseInfo PostProcess(XElement item, ReleaseInfo releaseInfo)
{
var enclosureType = item.Element("enclosure").Attribute("type").Value;
if (!enclosureType.Contains("application/x-bittorrent"))
{
throw new UnsupportedFeedException("Feed contains {0} instead of application/x-bittorrent", enclosureType);
}
return base.PostProcess(item, releaseInfo);
}
protected override string GetInfoUrl(XElement item)
{
return ParseUrl(item.TryGetValue("comments").TrimEnd("#comments"));

@ -41,6 +41,7 @@ namespace NzbDrone.Core.Indexers.Torznab
});
RuleFor(c => c.BaseUrl).ValidRootUrl();
RuleFor(c => c.ApiPath).ValidUrlBase("/api");
RuleFor(c => c.ApiKey).NotEmpty().When(ShouldHaveApiKey);
RuleFor(c => c.AdditionalParameters).Matches(AdditionalParametersRegex)
.When(c => !c.AdditionalParameters.IsNullOrWhiteSpace());
@ -56,7 +57,7 @@ namespace NzbDrone.Core.Indexers.Torznab
MinimumSeeders = IndexerDefaults.MINIMUM_SEEDERS;
}
[FieldDefinition(5, Type = FieldType.Textbox, Label = "Minimum Seeders", HelpText = "Minimum number of seeders required.", Advanced = true)]
[FieldDefinition(6, Type = FieldType.Textbox, Label = "Minimum Seeders", HelpText = "Minimum number of seeders required.", Advanced = true)]
public int MinimumSeeders { get; set; }
public override NzbDroneValidationResult Validate()

@ -669,6 +669,7 @@
<Compile Include="Indexers\Rarbg\RarbgSettings.cs" />
<Compile Include="Indexers\Rarbg\RarbgParser.cs" />
<Compile Include="Indexers\Rarbg\RarbgTokenProvider.cs" />
<Compile Include="Indexers\RssEnclosure.cs" />
<Compile Include="Indexers\XmlCleaner.cs" />
<Compile Include="Indexers\RssIndexerRequestGenerator.cs" />
<Compile Include="Indexers\RssParser.cs" />

@ -34,9 +34,9 @@ namespace NzbDrone.Core.Validation
return ruleBuilder.SetValidator(new RegularExpressionValidator("^https?://[-_a-z0-9.]+", RegexOptions.IgnoreCase)).WithMessage("must be valid URL that starts with http(s)://");
}
public static IRuleBuilderOptions<T, string> ValidUrlBase<T>(this IRuleBuilder<T, string> ruleBuilder)
public static IRuleBuilderOptions<T, string> ValidUrlBase<T>(this IRuleBuilder<T, string> ruleBuilder, string example = "/sonarr")
{
return ruleBuilder.SetValidator(new RegularExpressionValidator(@"^(?!\/?https?://[-_a-z0-9.]+)", RegexOptions.IgnoreCase)).WithMessage("Must be a valid URL path (ie: '/sonarr')");
return ruleBuilder.SetValidator(new RegularExpressionValidator(@"^(?!\/?https?://[-_a-z0-9.]+)", RegexOptions.IgnoreCase)).WithMessage($"Must be a valid URL path (ie: '{example}')");
}
public static IRuleBuilderOptions<T, int> ValidPort<T>(this IRuleBuilder<T, int> ruleBuilder)

Loading…
Cancel
Save