mirror of
https://github.com/Radarr/Radarr.git
synced 2024-09-11 12:02:35 +02:00
Fixed: Processing of mixed newznab/torznab api such as the experimental animetosho api.
Ref #1384
This commit is contained in:
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 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 @@ public void should_parse_recent_feed_from_newznab_nzb_su()
|
||||
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 @@ public void should_detect_rss_settings_for_AnimeTosho_without_size()
|
||||
[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 @@ public void should_parse_recent_feed_from_torznab_tpb()
|
||||
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 @@ protected virtual IList<ReleaseInfo> FetchReleases(Func<IIndexerRequestGenerator
|
||||
}
|
||||
}
|
||||
|
||||
releases.AddRange(pagedReleases);
|
||||
releases.AddRange(pagedReleases.Where(IsValidRelease));
|
||||
}
|
||||
|
||||
if (releases.Any())
|
||||
@ -268,6 +268,16 @@ protected virtual IList<ReleaseInfo> FetchReleases(Func<IIndexerRequestGenerator
|
||||
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 @@ protected virtual IList<ReleaseInfo> CleanupReleases(IEnumerable<ReleaseInfo> re
|
||||
|
||||
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 @@ private NewznabCapabilities FetchCapabilities(NewznabSettings indexerSettings)
|
||||
{
|
||||
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 @@ public virtual IndexerPageableRequestChain GetRecentRequests()
|
||||
{
|
||||
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 @@ private IEnumerable<IndexerRequest> GetPagedRequests(int maxPages, IEnumerable<i
|
||||
|
||||
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 @@ public class NewznabRssParser : RssParser
|
||||
|
||||
public NewznabRssParser()
|
||||
{
|
||||
PreferredEnclosureMimeType = "application/x-nzb";
|
||||
PreferredEnclosureMimeTypes = UsenetEnclosureMimeTypes;
|
||||
UseEnclosureUrl = true;
|
||||
}
|
||||
|
||||
protected override bool PreProcess(IndexerResponse indexerResponse)
|
||||
@ -45,6 +47,24 @@ protected override bool PreProcess(IndexerResponse indexerResponse)
|
||||
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 @@ protected override ReleaseInfo ProcessItem(XElement item, ReleaseInfo releaseInf
|
||||
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 @@ protected override DateTime GetPublishDate(XElement item)
|
||||
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 @@ public NewznabSettingsValidator()
|
||||
});
|
||||
|
||||
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 @@ public class NewznabSettings : IIndexerSettings
|
||||
|
||||
public NewznabSettings()
|
||||
{
|
||||
ApiPath = "/api";
|
||||
Categories = new[] { 5030, 5040 };
|
||||
AnimeCategories = Enumerable.Empty<int>();
|
||||
}
|
||||
@ -67,19 +69,22 @@ public NewznabSettings()
|
||||
[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()
|
||||
|
14
src/NzbDrone.Core/Indexers/RssEnclosure.cs
Normal file
14
src/NzbDrone.Core/Indexers/RssEnclosure.cs
Normal file
@ -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 @@ public class RssParser : IParseIndexerResponse
|
||||
// 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 @@ public virtual IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
|
||||
}
|
||||
|
||||
var document = LoadXmlDocument(indexerResponse);
|
||||
var items = GetItems(document);
|
||||
var items = GetItems(document).ToList();
|
||||
|
||||
foreach (var item in items)
|
||||
{
|
||||
@ -77,6 +82,11 @@ public virtual IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
|
||||
}
|
||||
}
|
||||
|
||||
if (!PostProcess(indexerResponse, items, releases))
|
||||
{
|
||||
return new List<ReleaseInfo>();
|
||||
}
|
||||
|
||||
return releases;
|
||||
}
|
||||
|
||||
@ -124,6 +134,11 @@ protected virtual bool PreProcess(IndexerResponse indexerResponse)
|
||||
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 @@ protected ReleaseInfo ProcessItem(XElement item)
|
||||
|
||||
_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 @@ protected virtual ReleaseInfo ProcessItem(XElement item, ReleaseInfo releaseInfo
|
||||
return releaseInfo;
|
||||
}
|
||||
|
||||
protected virtual ReleaseInfo PostProcess(XElement item, ReleaseInfo releaseInfo)
|
||||
protected virtual ReleaseInfo PostProcessItem(XElement item, ReleaseInfo releaseInfo)
|
||||
{
|
||||
return releaseInfo;
|
||||
}
|
||||
@ -187,7 +202,8 @@ protected virtual string GetDownloadUrl(XElement item)
|
||||
{
|
||||
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,37 +244,59 @@ protected virtual long GetEnclosureLength(XElement item)
|
||||
|
||||
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").ToArray();
|
||||
var enclosures = item.Elements("enclosure")
|
||||
.Select(v => new RssEnclosure
|
||||
{
|
||||
Url = v.Attribute("url").Value,
|
||||
Type = v.Attribute("type").Value,
|
||||
Length = (long)v.Attribute("length")
|
||||
})
|
||||
.ToArray();
|
||||
|
||||
return enclosures;
|
||||
}
|
||||
|
||||
protected RssEnclosure GetEnclosure(XElement item, bool enforceMimeType = true)
|
||||
{
|
||||
var enclosures = GetEnclosures(item);
|
||||
|
||||
return GetEnclosure(enclosures, enforceMimeType);
|
||||
}
|
||||
|
||||
protected virtual RssEnclosure GetEnclosure(RssEnclosure[] enclosures, bool enforceMimeType = true)
|
||||
{
|
||||
if (enclosures.Length == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (enclosures.Length == 1)
|
||||
if (PreferredEnclosureMimeTypes != null)
|
||||
{
|
||||
return enclosures.First();
|
||||
}
|
||||
|
||||
if (PreferredEnclosureMimeType != null)
|
||||
{
|
||||
var preferredEnclosure = enclosures.FirstOrDefault(v => v.Attribute("type").Value == PreferredEnclosureMimeType);
|
||||
|
||||
if (preferredEnclosure != null)
|
||||
foreach (var preferredEnclosureType in PreferredEnclosureMimeTypes)
|
||||
{
|
||||
return preferredEnclosure;
|
||||
var preferredEnclosure = enclosures.FirstOrDefault(v => v.Type == preferredEnclosureType);
|
||||
|
||||
if (preferredEnclosure != null)
|
||||
{
|
||||
return preferredEnclosure;
|
||||
}
|
||||
}
|
||||
|
||||
if (enforceMimeType)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return item.Elements("enclosure").SingleOrDefault();
|
||||
return enclosures.SingleOrDefault();
|
||||
}
|
||||
|
||||
protected IEnumerable<XElement> GetItems(XDocument document)
|
||||
|
@ -16,7 +16,7 @@ public class TorrentRssParser : RssParser
|
||||
|
||||
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 @@ public class TorznabRssParser : TorrentRssParser
|
||||
{
|
||||
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 @@ protected override bool PreProcess(IndexerResponse indexerResponse)
|
||||
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 @@ protected override ReleaseInfo ProcessItem(XElement item, ReleaseInfo releaseInf
|
||||
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 @@ public TorznabSettingsValidator()
|
||||
});
|
||||
|
||||
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 @@ public TorznabSettings()
|
||||
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 @@ public static IRuleBuilderOptions<T, string> ValidRootUrl<T>(this IRuleBuilder<T
|
||||
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)
|
||||
@ -68,4 +68,4 @@ public static IRuleBuilderOptions<T, TProp> AsWarning<T, TProp>(this IRuleBuilde
|
||||
return ruleBuilder.WithState(v => NzbDroneValidationState.Warning);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user