Support for Gazelle Indexers (RED, AR)

pull/7/head
Qstick 3 years ago
parent 0fb7168669
commit 30367b53fd

@ -17,7 +17,6 @@ namespace NzbDrone.Core.Test.IndexerTests.FileListTests
{
Subject.Settings = new FileListSettings()
{
BaseUrl = "http://127.0.0.1:1234/",
Passkey = "abcd",
Username = "somename"
};

@ -18,7 +18,6 @@ namespace NzbDrone.Core.Test.IndexerTests.HDBitsTests
{
Subject.Settings = new HDBitsSettings()
{
BaseUrl = "http://127.0.0.1:1234/",
ApiKey = "abcd",
Username = "somename"
};

@ -1,117 +0,0 @@
using System;
using System.Linq;
using System.Net;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Common.Http;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Indexers.IPTorrents;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.IndexerTests.IPTorrentsTests
{
[TestFixture]
public class IPTorrentsFixture : CoreTest<IPTorrents>
{
[SetUp]
public void Setup()
{
Subject.Definition = new IndexerDefinition()
{
Name = "IPTorrents",
Settings = new IPTorrentsSettings() { BaseUrl = "http://fake.com/" }
};
}
private void GivenOldFeedFormat()
{
Subject.Definition = new IndexerDefinition()
{
Name = "IPTorrents",
Settings = new IPTorrentsSettings() { BaseUrl = "https://iptorrents.com/torrents/rss?u=snip;tp=snip;3;80;93;37;download" }
};
}
private void GivenNewFeedFormat()
{
Subject.Definition = new IndexerDefinition()
{
Name = "IPTorrents",
Settings = new IPTorrentsSettings() { BaseUrl = "https://iptorrents.com/t.rss?u=USERID;tp=APIKEY;3;80;93;37;download" }
};
}
private void GivenFeedNoDownloadFormat()
{
Subject.Definition = new IndexerDefinition()
{
Name = "IPTorrents",
Settings = new IPTorrentsSettings() { BaseUrl = "https://iptorrents.com/t.rss?u=USERID;tp=APIKEY;3;80;93;37" }
};
}
[Test]
public void should_validate_old_feed_format()
{
GivenOldFeedFormat();
var validationResult = Subject.Definition.Settings.Validate();
validationResult.IsValid.Should().BeTrue();
}
[Test]
public void should_validate_new_feed_format()
{
GivenNewFeedFormat();
var validationResult = Subject.Definition.Settings.Validate();
validationResult.IsValid.Should().BeTrue();
}
[Test]
public void should_not_validate_bad_format()
{
var validationResult = Subject.Definition.Settings.Validate();
validationResult.IsValid.Should().BeFalse();
}
[Test]
public void should_not_validate_no_download_format()
{
GivenFeedNoDownloadFormat();
var validationResult = Subject.Definition.Settings.Validate();
validationResult.IsValid.Should().BeFalse();
}
[Test]
public void should_parse_recent_feed_from_IPTorrents()
{
var recentFeed = ReadAllText(@"Files/Indexers/IPTorrents/IPTorrents.xml");
Mocker.GetMock<IHttpClient>()
.Setup(o => o.Execute(It.Is<HttpRequest>(v => v.Method == HttpMethod.GET)))
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), new CookieCollection(), recentFeed));
var releases = Subject.Fetch(new MovieSearchCriteria { Categories = new int[] { 2000 } }).Releases;
releases.Should().HaveCount(5);
releases.First().Should().BeOfType<TorrentInfo>();
var torrentInfo = releases.First() as TorrentInfo;
torrentInfo.Title.Should().Be("24 S03E12 720p WEBRip h264-DRAWER");
torrentInfo.DownloadProtocol.Should().Be(DownloadProtocol.Torrent);
torrentInfo.DownloadUrl.Should().Be("http://iptorrents.com/download.php/1234/24.S03E12.720p.WEBRip.h264-DRAWER.torrent?torrent_pass=abcd");
torrentInfo.InfoUrl.Should().BeNullOrEmpty();
torrentInfo.CommentUrl.Should().BeNullOrEmpty();
torrentInfo.Indexer.Should().Be(Subject.Definition.Name);
torrentInfo.PublishDate.Should().Be(DateTime.Parse("2014/05/12 19:06:34"));
torrentInfo.Size.Should().Be(1471026299);
torrentInfo.InfoHash.Should().Be(null);
torrentInfo.MagnetUrl.Should().Be(null);
torrentInfo.Peers.Should().Be(null);
torrentInfo.Seeders.Should().Be(null);
}
}
}

@ -1,59 +0,0 @@
using System;
using System.Linq;
using System.Net;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Common.Http;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Indexers.Nyaa;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.IndexerTests.NyaaTests
{
[TestFixture]
public class NyaaFixture : CoreTest<Nyaa>
{
[SetUp]
public void Setup()
{
Subject.Definition = new IndexerDefinition()
{
Name = "Nyaa",
Settings = new NyaaSettings()
};
}
[Test]
public void should_parse_recent_feed_from_Nyaa()
{
var recentFeed = ReadAllText(@"Files/Indexers/Nyaa/Nyaa.xml");
Mocker.GetMock<IHttpClient>()
.Setup(o => o.Execute(It.Is<HttpRequest>(v => v.Method == HttpMethod.GET)))
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), new CookieCollection(), recentFeed));
var releases = Subject.Fetch(new MovieSearchCriteria()).Releases;
releases.Should().HaveCount(4);
releases.First().Should().BeOfType<TorrentInfo>();
var torrentInfo = releases.First() as TorrentInfo;
torrentInfo.Title.Should().Be("[TSRaws] Futsuu no Joshikousei ga [Locodol] Yattemita. #07 (TBS).ts");
torrentInfo.DownloadProtocol.Should().Be(DownloadProtocol.Torrent);
torrentInfo.DownloadUrl.Should().Be("http://www.nyaa.si/?page=download&tid=587750");
torrentInfo.InfoUrl.Should().Be("http://www.nyaa.si/?page=view&tid=587750");
torrentInfo.CommentUrl.Should().BeNullOrEmpty();
torrentInfo.Indexer.Should().Be(Subject.Definition.Name);
torrentInfo.PublishDate.Should().Be(DateTime.Parse("2014/08/14 18:10:36"));
torrentInfo.Size.Should().Be(2523293286); //2.35 GiB
torrentInfo.InfoHash.Should().Be(null);
torrentInfo.MagnetUrl.Should().Be(null);
torrentInfo.Peers.Should().Be(2 + 1);
torrentInfo.Seeders.Should().Be(1);
}
}
}

@ -27,7 +27,7 @@ namespace NzbDrone.Core.Test.IndexerTests.RarbgTests
};
Mocker.GetMock<IRarbgTokenProvider>()
.Setup(v => v.GetToken(It.IsAny<RarbgSettings>()))
.Setup(v => v.GetToken(It.IsAny<RarbgSettings>(), It.IsAny<string>()))
.Returns("validtoken");
}

@ -8,6 +8,7 @@ namespace NzbDrone.Core.Test.IndexerTests
public class TestIndexer : HttpIndexerBase<TestIndexerSettings>
{
public override string Name => "Test Indexer";
public override string BaseUrl => "http://testindexer.com";
public override DownloadProtocol Protocol => DownloadProtocol.Usenet;

@ -1,11 +1,12 @@
using System;
using System.Collections.Generic;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Test.IndexerTests
{
public class TestIndexerSettings : IIndexerSettings
public class TestIndexerSettings : IProviderConfig
{
public NzbDroneValidationResult Validate()
{
@ -13,7 +14,5 @@ namespace NzbDrone.Core.Test.IndexerTests
}
public string BaseUrl { get; set; }
public IEnumerable<int> MultiLanguages { get; set; }
}
}

@ -1,60 +0,0 @@
using System.Collections.Generic;
using FluentValidation.Results;
using NLog;
using NLog.Config;
using NLog.Targets;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Indexers.TorrentRss;
using NzbDrone.Core.Parser;
namespace NzbDrone.Core.Test.IndexerTests.TorrentRssIndexerTests
{
public class TestTorrentRssIndexer : TorrentRssIndexer
{
public TestTorrentRssIndexer(ITorrentRssParserFactory torrentRssParserFactory, IHttpClient httpClient, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
: base(torrentRssParserFactory, httpClient, indexerStatusService, configService, logger)
{
}
public List<ValidationFailure> TestPublic()
{
var result = new List<ValidationFailure>();
SetupNLog(); // Enable this to enable trace logging with nlog for debugging purposes
Test(result);
return result;
}
/// <summary>
/// Code to quickly debug unit tests
/// </summary>
private void SetupNLog()
{
// Step 1. Create configuration object
var config = new LoggingConfiguration();
var fileTarget = new FileTarget();
config.AddTarget("file", fileTarget);
// Step 3. Set target properties
fileTarget.FileName = "${basedir}/log.txt";
fileTarget.Layout = GetStandardLayout();
// Step 4. Define rules
var rule1 = new LoggingRule("*", LogLevel.Trace, fileTarget);
config.LoggingRules.Add(rule1);
// Step 5. Activate the configuration
LogManager.Configuration = config;
}
private static string GetStandardLayout()
{
return @"${date:universalTime=true:format=u}|" + "${processid:fixedLength=True:padding=4:padCharacter= }|"
+ "${threadid:fixedLength=True:padding=3:padCharacter= }|" + "${level:fixedLength=True:padding=5:padCharacter= :upperCase=True}|"
+ "${callsite:fileName=True:className=False:methodName=True:includeSourcePath=False:padding=50:padCharacter= }|" + "${message}"
+ " ${exception:maxInnerExceptionLevel=3:format=Method,Message,StackTrace:innerFormat=Method,Message,StackTrace:separator=\r\n:innerExceptionSeparator=\r\n}";
}
}
}

@ -1,304 +0,0 @@
using System;
using System.Linq;
using System.Net;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Common.Http;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Indexers.TorrentRss;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.IndexerTests.TorrentRssIndexerTests
{
[TestFixture]
public class TorrentRssIndexerFixture : CoreTest<TestTorrentRssIndexer>
{
private const string _indexerUrl = "http://my.indexer.tv/recent";
[SetUp]
public void Setup()
{
Mocker.SetConstant<ITorrentRssSettingsDetector>(Mocker.Resolve<TorrentRssSettingsDetector>());
Mocker.SetConstant<ITorrentRssParserFactory>(Mocker.Resolve<TorrentRssParserFactory>());
Subject.Definition = new IndexerDefinition()
{
Name = "TorrentRssIndexer",
Settings = new TorrentRssIndexerSettings() { BaseUrl = _indexerUrl },
};
}
private void GivenRecentFeedResponse(string rssXmlFile)
{
var recentFeed = ReadAllText(@"Files/Indexers/" + rssXmlFile);
Mocker.GetMock<IHttpClient>()
.Setup(o => o.Execute(It.IsAny<HttpRequest>()))
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), new CookieCollection(), recentFeed));
}
[Test]
public void should_parse_recent_feed_from_ImmortalSeed()
{
GivenRecentFeedResponse("TorrentRss/ImmortalSeed.xml");
var releases = Subject.Fetch(new MovieSearchCriteria { Categories = new int[] { 2000 } }).Releases;
releases.Should().HaveCount(50);
releases.First().Should().BeOfType<TorrentInfo>();
var torrentInfo = (TorrentInfo)releases.First();
torrentInfo.Title.Should().Be("Conan.2015.02.05.Jeff.Bridges.720p.HDTV.X264-CROOKS");
torrentInfo.DownloadProtocol.Should().Be(DownloadProtocol.Torrent);
torrentInfo.DownloadUrl.Should().Be("https://immortalseed.me/download.php?type=rss&secret_key=12345678910&id=374534");
torrentInfo.InfoUrl.Should().BeNullOrEmpty();
torrentInfo.CommentUrl.Should().BeNullOrEmpty();
torrentInfo.Indexer.Should().Be(Subject.Definition.Name);
torrentInfo.PublishDate.Should().Be(DateTime.Parse("2015-02-06 13:32:26"));
torrentInfo.Size.Should().Be(984078090);
torrentInfo.InfoHash.Should().BeNullOrEmpty();
torrentInfo.MagnetUrl.Should().BeNullOrEmpty();
torrentInfo.Peers.Should().Be(8);
torrentInfo.Seeders.Should().Be(6);
}
[Test]
public void should_parse_recent_feed_from_Ezrss()
{
GivenRecentFeedResponse("TorrentRss/Ezrss.xml");
var releases = Subject.Fetch(new MovieSearchCriteria { Categories = new int[] { 2000 } }).Releases;
releases.Should().HaveCount(3);
releases.First().Should().BeOfType<TorrentInfo>();
var torrentInfo = releases.First() as TorrentInfo;
torrentInfo.Title.Should().Be("S4C I Grombil Cyfandir Pell American Interior [PDTV - MVGROUP]");
torrentInfo.DownloadProtocol.Should().Be(DownloadProtocol.Torrent);
torrentInfo.DownloadUrl.Should().Be("http://re.zoink.it/20a4ed4eFC");
torrentInfo.InfoUrl.Should().Be("http://eztv.it/ep/58439/s4c-i-grombil-cyfandir-pell-american-interior-pdtv-x264-mvgroup/");
torrentInfo.CommentUrl.Should().Be("http://eztv.it/forum/discuss/58439/");
torrentInfo.Indexer.Should().Be(Subject.Definition.Name);
torrentInfo.PublishDate.Should().Be(DateTime.Parse("2014/09/15 18:39:00"));
torrentInfo.Size.Should().Be(796606175);
torrentInfo.InfoHash.Should().Be("20FC4FBFA88272274AC671F857CC15144E9AA83E");
torrentInfo.MagnetUrl.Should().Be("magnet:?xt=urn:btih:ED6E7P5IQJZCOSWGOH4FPTAVCRHJVKB6&dn=S4C.I.Grombil.Cyfandir.Pell.American.Interior.PDTV.x264-MVGroup");
torrentInfo.Peers.Should().NotHaveValue();
torrentInfo.Seeders.Should().NotHaveValue();
}
[Test]
public void should_parse_recent_feed_from_ShowRSS_info()
{
Subject.Definition.Settings.As<TorrentRssIndexerSettings>().AllowZeroSize = true;
GivenRecentFeedResponse("TorrentRss/ShowRSS.info.xml");
var releases = Subject.Fetch(new MovieSearchCriteria { Categories = new int[] { 2000 } }).Releases;
releases.Should().HaveCount(5);
releases.First().Should().BeOfType<TorrentInfo>();
var torrentInfo = releases.First() as TorrentInfo;
torrentInfo.Title.Should().Be("The Voice 8x25");
torrentInfo.DownloadProtocol.Should().Be(DownloadProtocol.Torrent);
torrentInfo.DownloadUrl.Should().Be("magnet:?xt=urn:btih:96CD620BEDA3EFD7C4D7746EF94549D03A2EB13B&dn=The+Voice+S08E25+WEBRip+x264+WNN&tr=udp://tracker.coppersurfer.tk:6969/announce&tr=udp://tracker.leechers-paradise.org:6969&tr=udp://open.demonii.com:1337");
torrentInfo.InfoUrl.Should().BeNullOrEmpty();
torrentInfo.CommentUrl.Should().BeNullOrEmpty();
torrentInfo.Indexer.Should().Be(Subject.Definition.Name);
torrentInfo.PublishDate.Should().Be(DateTime.Parse("2015/05/15 08:30:01"));
torrentInfo.Size.Should().Be(0);
torrentInfo.InfoHash.Should().Be("96CD620BEDA3EFD7C4D7746EF94549D03A2EB13B");
torrentInfo.MagnetUrl.Should().Be("magnet:?xt=urn:btih:96CD620BEDA3EFD7C4D7746EF94549D03A2EB13B&dn=The+Voice+S08E25+WEBRip+x264+WNN&tr=udp://tracker.coppersurfer.tk:6969/announce&tr=udp://tracker.leechers-paradise.org:6969&tr=udp://open.demonii.com:1337");
torrentInfo.Peers.Should().NotHaveValue();
torrentInfo.Seeders.Should().NotHaveValue();
}
[Test]
public void should_parse_recent_feed_from_Doki()
{
Subject.Definition.Settings.As<TorrentRssIndexerSettings>().AllowZeroSize = true;
GivenRecentFeedResponse("TorrentRss/Doki.xml");
var releases = Subject.Fetch(new MovieSearchCriteria { Categories = new int[] { 2000 } }).Releases;
releases.Should().HaveCount(5);
releases.First().Should().BeOfType<TorrentInfo>();
var torrentInfo = releases.First() as TorrentInfo;
torrentInfo.Title.Should().Be("[Doki] PriPara 50 (848x480 h264 AAC) [6F0B49FD] mkv");
torrentInfo.DownloadProtocol.Should().Be(DownloadProtocol.Torrent);
torrentInfo.DownloadUrl.Should().Be("http://tracker.anime-index.org/download.php?id=82d8ad84403e01a7786130905ca169a3429e657f&f=%5BDoki%5D+PriPara+-+50+%28848x480+h264+AAC%29+%5B6F0B49FD%5D.mkv.torrent");
torrentInfo.InfoUrl.Should().BeNullOrEmpty();
torrentInfo.CommentUrl.Should().BeNullOrEmpty();
torrentInfo.Indexer.Should().Be(Subject.Definition.Name);
torrentInfo.PublishDate.Should().Be(DateTime.Parse("Thu, 02 Jul 2015 08:18:29 GMT").ToUniversalTime());
torrentInfo.Size.Should().Be(0);
torrentInfo.InfoHash.Should().BeNull();
torrentInfo.MagnetUrl.Should().BeNull();
torrentInfo.Peers.Should().NotHaveValue();
torrentInfo.Seeders.Should().NotHaveValue();
}
[Test]
public void should_parse_recent_feed_from_ExtraTorrents()
{
GivenRecentFeedResponse("TorrentRss/ExtraTorrents.xml");
var releases = Subject.Fetch(new MovieSearchCriteria { Categories = new int[] { 2000 } }).Releases;
releases.Should().HaveCount(5);
releases.First().Should().BeOfType<TorrentInfo>();
var torrentInfo = releases.First() as TorrentInfo;
torrentInfo.Title.Should().Be("One.Piece.E334.D ED.720p.HDTV.x264-W4F-={SPARROW}=-");
torrentInfo.DownloadProtocol.Should().Be(DownloadProtocol.Torrent);
torrentInfo.DownloadUrl.Should().Be("http://ac.me/download/4722030/One.Piece.E334.D+ED.720p.HDTV.x264-W4F-%3D%7BSPARROW%7D%3D-.torrent");
torrentInfo.InfoUrl.Should().BeNullOrEmpty();
torrentInfo.CommentUrl.Should().BeNullOrEmpty();
torrentInfo.Indexer.Should().Be(Subject.Definition.Name);
torrentInfo.PublishDate.Should().Be(DateTime.Parse("Sun, 21 Feb 2016 09:51:54 +0000").ToUniversalTime());
torrentInfo.Size.Should().Be(562386947);
torrentInfo.InfoHash.Should().BeNull();
torrentInfo.MagnetUrl.Should().BeNull();
torrentInfo.Peers.Should().NotHaveValue();
torrentInfo.Seeders.Should().NotHaveValue();
}
[Test]
public void should_parse_recent_feed_from_LimeTorrents()
{
GivenRecentFeedResponse("TorrentRss/LimeTorrents.xml");
var releases = Subject.Fetch(new MovieSearchCriteria { Categories = new int[] { 2000 } }).Releases;
releases.Should().HaveCount(5);
releases.First().Should().BeOfType<TorrentInfo>();
var torrentInfo = releases.First() as TorrentInfo;
torrentInfo.Title.Should().Be("The Expanse 2x04 (720p-HDTV-x264-SVA)[VTV]");
torrentInfo.DownloadProtocol.Should().Be(DownloadProtocol.Torrent);
torrentInfo.DownloadUrl.Should().Be("http://itorrents.org/torrent/51C578C9823DD58F6EEA287C368ED935843D63AB.torrent?title=The-Expanse-2x04-(720p-HDTV-x264-SVA)[VTV]");
torrentInfo.InfoUrl.Should().BeNullOrEmpty();
torrentInfo.CommentUrl.Should().Be("http://www.limetorrents.cc/The-Expanse-2x04-(720p-HDTV-x264-SVA)[VTV]-torrent-8643587.html");
torrentInfo.Indexer.Should().Be(Subject.Definition.Name);
torrentInfo.PublishDate.Should().Be(DateTime.Parse("16 Feb 2017 05:24:26 +0300").ToUniversalTime());
torrentInfo.Size.Should().Be(880496711);
torrentInfo.InfoHash.Should().BeNull();
torrentInfo.MagnetUrl.Should().BeNull();
torrentInfo.Peers.Should().NotHaveValue();
torrentInfo.Seeders.Should().NotHaveValue();
}
[Test]
public void should_parse_recent_feed_from_AnimeTosho_without_size()
{
GivenRecentFeedResponse("TorrentRss/AnimeTosho_NoSize.xml");
var releases = Subject.Fetch(new MovieSearchCriteria { Categories = new int[] { 2000 } }).Releases;
releases.Should().HaveCount(2);
releases.First().Should().BeOfType<TorrentInfo>();
var torrentInfo = releases.First() as TorrentInfo;
torrentInfo.Title.Should().Be("[FFF] Ore Monogatari!! - Vol.01 [BD][720p-AAC]");
torrentInfo.DownloadProtocol.Should().Be(DownloadProtocol.Torrent);
torrentInfo.DownloadUrl.Should().Be("http://storage.animetosho.org/torrents/85a570f25067f69b3c83b901ce6c00c491345288/%5BFFF%5D%20Ore%20Monogatari%21%21%20-%20Vol.01%20%5BBD%5D%5B720p-AAC%5D.torrent");
torrentInfo.InfoUrl.Should().BeNullOrEmpty();
torrentInfo.CommentUrl.Should().Be("https://animetosho.org/view/fff-ore-monogatari-vol-01-bd-720p-aac.1009077");
torrentInfo.Indexer.Should().Be(Subject.Definition.Name);
torrentInfo.PublishDate.Should().Be(DateTime.Parse("Tue, 02 Aug 2016 13:48:04 +0000").ToUniversalTime());
torrentInfo.Size.Should().Be((long)Math.Round(1.366D * 1024L * 1024L * 1024L));
torrentInfo.InfoHash.Should().BeNull();
torrentInfo.MagnetUrl.Should().BeNull();
torrentInfo.Peers.Should().NotHaveValue();
torrentInfo.Seeders.Should().NotHaveValue();
}
[Test]
public void should_parse_multi_enclosure_from_AnimeTosho()
{
GivenRecentFeedResponse("TorrentRss/AnimeTosho_NoSize.xml");
var releases = Subject.Fetch(new MovieSearchCriteria { Categories = new int[] { 2000 } }).Releases;
releases.Should().HaveCount(2);
releases.Last().Should().BeOfType<TorrentInfo>();
var torrentInfo = releases.Last() as TorrentInfo;
torrentInfo.Title.Should().Be("DAYS - 05 (1280x720 HEVC2 AAC).mkv");
torrentInfo.DownloadProtocol.Should().Be(DownloadProtocol.Torrent);
torrentInfo.DownloadUrl.Should().Be("http://storage.animetosho.org/torrents/4b58360143d59a55cbd922397a3eaa378165f3ff/DAYS%20-%2005%20%281280x720%20HEVC2%20AAC%29.torrent");
}
[Test]
public void should_parse_recent_feed_from_AlphaRatio()
{
GivenRecentFeedResponse("TorrentRss/AlphaRatio.xml");
var releases = Subject.Fetch(new MovieSearchCriteria { Categories = new int[] { 2000 } }).Releases;
releases.Should().HaveCount(2);
releases.Last().Should().BeOfType<TorrentInfo>();
var torrentInfo = releases.Last() as TorrentInfo;
torrentInfo.Title.Should().Be("TvHD 465860 465831 WWE.RAW.2016.11.28.720p.HDTV.x264-KYR");
torrentInfo.DownloadProtocol.Should().Be(DownloadProtocol.Torrent);
torrentInfo.DownloadUrl.Should().Be("https://alpharatio.cc/torrents.php?action=download&authkey=private_auth_key&torrent_pass=private_torrent_pass&id=465831");
}
[Test]
public void should_parse_recent_feed_from_EveolutionWorld_without_size()
{
Subject.Definition.Settings.As<TorrentRssIndexerSettings>().AllowZeroSize = true;
GivenRecentFeedResponse("TorrentRss/EvolutionWorld.xml");
var releases = Subject.Fetch(new MovieSearchCriteria { Categories = new int[] { 2000 } }).Releases;
releases.Should().HaveCount(2);
releases.First().Should().BeOfType<TorrentInfo>();
var torrentInfo = releases.First() as TorrentInfo;
torrentInfo.Title.Should().Be("[TVShow --> TVShow Bluray 720p] Fargo S01 Complete Season 1 720p BRRip DD5.1 x264-PSYPHER [SEEDERS (3)/LEECHERS (0)]");
torrentInfo.DownloadProtocol.Should().Be(DownloadProtocol.Torrent);
torrentInfo.DownloadUrl.Should().Be("http://ew.pw/download.php?id=dea071a7a62a0d662538d46402fb112f30b8c9fa&f=Fargo%20S01%20Complete%20Season%201%20720p%20BRRip%20DD5.1%20x264-PSYPHER.torrent&auth=secret");
torrentInfo.InfoUrl.Should().BeNullOrEmpty();
torrentInfo.CommentUrl.Should().BeNullOrEmpty();
torrentInfo.Indexer.Should().Be(Subject.Definition.Name);
torrentInfo.PublishDate.Should().Be(DateTime.Parse("2017-08-13T22:21:43Z").ToUniversalTime());
torrentInfo.Size.Should().Be(0);
torrentInfo.InfoHash.Should().BeNull();
torrentInfo.MagnetUrl.Should().BeNull();
torrentInfo.Peers.Should().NotHaveValue();
torrentInfo.Seeders.Should().NotHaveValue();
}
[Test]
public void should_record_indexer_failure_if_unsupported_feed()
{
GivenRecentFeedResponse("TorrentRss/invalid/TorrentDay_NoPubDate.xml");
Subject.Fetch(new MovieSearchCriteria { Categories = new int[] { 2000 } }).Releases.Should().BeEmpty();
Mocker.GetMock<IIndexerStatusService>()
.Verify(v => v.RecordFailure(It.IsAny<int>(), TimeSpan.Zero), Times.Once());
ExceptionVerification.ExpectedErrors(1);
}
}
}

@ -1,164 +0,0 @@
using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Common.Cache;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Indexers.Exceptions;
using NzbDrone.Core.Indexers.TorrentRss;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.IndexerTests.TorrentRssIndexerTests
{
[TestFixture]
public class TorrentRssParserFactoryFixture : CoreTest<TorrentRssParserFactory>
{
private TorrentRssIndexerSettings _indexerSettings1;
private TorrentRssIndexerSettings _indexerSettings2;
private TorrentRssIndexerSettings _indexerSettings3;
[SetUp]
public void SetUp()
{
Mocker.SetConstant<ICacheManager>(Mocker.Resolve<CacheManager>());
_indexerSettings1 = new TorrentRssIndexerSettings
{
BaseUrl = "http://my.indexer.com/"
};
_indexerSettings2 = new TorrentRssIndexerSettings
{
BaseUrl = "http://my.other.indexer.com/"
};
_indexerSettings3 = new TorrentRssIndexerSettings
{
BaseUrl = "http://my.indexer.com/",
AllowZeroSize = true
};
}
private void GivenSuccessful(TorrentRssIndexerParserSettings parserSettings = null)
{
if (parserSettings == null)
{
parserSettings = new TorrentRssIndexerParserSettings
{
UseEnclosureLength = true,
ParseSizeInDescription = false
};
}
Mocker.GetMock<ITorrentRssSettingsDetector>()
.Setup(v => v.Detect(It.IsAny<TorrentRssIndexerSettings>()))
.Returns(parserSettings);
}
private void GivenFailed()
{
Mocker.GetMock<ITorrentRssSettingsDetector>()
.Setup(v => v.Detect(It.IsAny<TorrentRssIndexerSettings>()))
.Returns((TorrentRssIndexerParserSettings)null);
}
private void VerifyDetectionCount(int count)
{
Mocker.GetMock<ITorrentRssSettingsDetector>()
.Verify(v => v.Detect(It.IsAny<TorrentRssIndexerSettings>()), Times.Exactly(count));
}
[Test]
public void should_return_ezrssparser()
{
GivenSuccessful(new TorrentRssIndexerParserSettings
{
UseEZTVFormat = true
});
var parser = Subject.GetParser(_indexerSettings1);
parser.Should().BeOfType<EzrssTorrentRssParser>();
}
[Test]
public void should_return_generic_torrentrssparser()
{
GivenSuccessful(new TorrentRssIndexerParserSettings
{
ParseSeedersInDescription = true,
ParseSizeInDescription = true,
SizeElementName = "Hello"
});
var parser = Subject.GetParser(_indexerSettings1);
parser.Should().BeOfType<TorrentRssParser>();
var rssParser = parser as TorrentRssParser;
rssParser.ParseSeedersInDescription.Should().BeTrue();
rssParser.ParseSizeInDescription.Should().BeTrue();
rssParser.SizeElementName.Should().Be("Hello");
}
[Test]
public void should_throw_on_failure()
{
GivenFailed();
Assert.Throws<UnsupportedFeedException>(() => Subject.GetParser(_indexerSettings1));
}
[Test]
public void should_cache_settings_for_same_baseurl()
{
GivenSuccessful();
var detection1 = Subject.GetParser(_indexerSettings1);
var detection2 = Subject.GetParser(_indexerSettings1);
detection1.Should().BeEquivalentTo(detection2);
VerifyDetectionCount(1);
}
[Test]
public void should_not_cache_failure()
{
GivenFailed();
Assert.Throws<UnsupportedFeedException>(() => Subject.GetParser(_indexerSettings1));
GivenSuccessful();
Subject.GetParser(_indexerSettings1);
VerifyDetectionCount(2);
}
[Test]
public void should_not_cache_settings_for_different_baseurl()
{
GivenSuccessful();
var detection1 = Subject.GetParser(_indexerSettings1);
var detection2 = Subject.GetParser(_indexerSettings2);
VerifyDetectionCount(2);
}
[Test]
public void should_not_cache_settings_for_different_settings()
{
GivenSuccessful();
var detection1 = Subject.GetParser(_indexerSettings1);
var detection2 = Subject.GetParser(_indexerSettings3);
VerifyDetectionCount(2);
}
}
}

@ -1,320 +0,0 @@
using System.Net;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Common.Http;
using NzbDrone.Core.Indexers.Exceptions;
using NzbDrone.Core.Indexers.TorrentRss;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.IndexerTests.TorrentRssIndexerTests
{
[TestFixture]
public class TorrentRssSettingsDetectorFixture : CoreTest<TorrentRssSettingsDetector>
{
private const string _indexerUrl = "http://my.indexer.tv/recent";
private TorrentRssIndexerSettings _indexerSettings;
[SetUp]
public void SetUp()
{
_indexerSettings = new TorrentRssIndexerSettings { BaseUrl = _indexerUrl };
}
private void GivenRecentFeedResponse(string rssXmlFile)
{
var recentFeed = ReadAllText(@"Files/Indexers/" + rssXmlFile);
Mocker.GetMock<IHttpClient>()
.Setup(o => o.Execute(It.IsAny<HttpRequest>()))
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), new CookieCollection(), recentFeed));
}
[Test]
public void should_detect_rss_settings_for_ezrss()
{
GivenRecentFeedResponse("TorrentRss/Ezrss.xml");
var settings = Subject.Detect(_indexerSettings);
settings.Should().BeEquivalentTo(new TorrentRssIndexerParserSettings
{
UseEZTVFormat = true,
UseEnclosureUrl = false,
UseEnclosureLength = false,
ParseSizeInDescription = false,
ParseSeedersInDescription = false,
SizeElementName = null
});
}
[Test]
public void should_detect_rss_settings_for_speed_cd()
{
GivenRecentFeedResponse("TorrentRss/speed.cd.xml");
var settings = Subject.Detect(_indexerSettings);
settings.Should().BeEquivalentTo(new TorrentRssIndexerParserSettings
{
UseEZTVFormat = false,
UseEnclosureUrl = false,
UseEnclosureLength = false,
ParseSizeInDescription = true,
ParseSeedersInDescription = false,
SizeElementName = null
});
}
[Test]
public void should_detect_rss_settings_for_ImmortalSeed()
{
GivenRecentFeedResponse("TorrentRss/ImmortalSeed.xml");
var settings = Subject.Detect(_indexerSettings);
settings.Should().BeEquivalentTo(new TorrentRssIndexerParserSettings
{
UseEZTVFormat = false,
UseEnclosureUrl = false,
UseEnclosureLength = false,
ParseSizeInDescription = true,
ParseSeedersInDescription = true,
SizeElementName = null
});
}
[Test]
public void should_detect_rss_settings_for_ShowRSS_info()
{
_indexerSettings.AllowZeroSize = true;
GivenRecentFeedResponse("TorrentRss/ShowRSS.info.xml");
var settings = Subject.Detect(_indexerSettings);
settings.Should().BeEquivalentTo(new TorrentRssIndexerParserSettings
{
UseEZTVFormat = false,
UseEnclosureUrl = true,
UseEnclosureLength = false,
ParseSizeInDescription = false,
ParseSeedersInDescription = false,
SizeElementName = null
});
}
[Test]
public void should_detect_rss_settings_for_TransmitTheNet()
{
GivenRecentFeedResponse("TorrentRss/TransmitTheNet.xml");
var settings = Subject.Detect(_indexerSettings);
settings.Should().BeEquivalentTo(new TorrentRssIndexerParserSettings
{
UseEZTVFormat = false,
UseEnclosureUrl = true,
UseEnclosureLength = false,
ParseSizeInDescription = true,
ParseSeedersInDescription = false,
SizeElementName = null
});
}
[Test]
public void should_detect_rss_settings_for_BitHdtv()
{
GivenRecentFeedResponse("TorrentRss/BitHdtv.xml");
var settings = Subject.Detect(_indexerSettings);
settings.Should().BeEquivalentTo(new TorrentRssIndexerParserSettings
{
UseEZTVFormat = false,
UseEnclosureUrl = false,
UseEnclosureLength = false,
ParseSizeInDescription = false,
ParseSeedersInDescription = false,
SizeElementName = "size"
});
}
[Test]
public void should_detect_rss_settings_for_Doki()
{
_indexerSettings.AllowZeroSize = true;
GivenRecentFeedResponse("TorrentRss/Doki.xml");
var settings = Subject.Detect(_indexerSettings);
settings.Should().BeEquivalentTo(new TorrentRssIndexerParserSettings
{
UseEZTVFormat = false,
UseEnclosureUrl = true,
UseEnclosureLength = false,
ParseSizeInDescription = false,
ParseSeedersInDescription = false,
SizeElementName = null
});
}
[Test]
public void should_detect_rss_settings_for_ExtraTorrents()
{
_indexerSettings.AllowZeroSize = true;
GivenRecentFeedResponse("TorrentRss/ExtraTorrents.xml");
var settings = Subject.Detect(_indexerSettings);
settings.Should().BeEquivalentTo(new TorrentRssIndexerParserSettings
{
UseEZTVFormat = false,
UseEnclosureUrl = true,
UseEnclosureLength = true,
ParseSizeInDescription = false,
ParseSeedersInDescription = false,
SizeElementName = null
});
}
[Test]
public void should_detect_rss_settings_for_LimeTorrents()
{
_indexerSettings.AllowZeroSize = true;
GivenRecentFeedResponse("TorrentRss/LimeTorrents.xml");
var settings = Subject.Detect(_indexerSettings);
settings.Should().BeEquivalentTo(new TorrentRssIndexerParserSettings
{
UseEZTVFormat = false,
UseEnclosureUrl = true,
UseEnclosureLength = true,
ParseSizeInDescription = false,
ParseSeedersInDescription = false,
SizeElementName = null
});
}
[Test]
public void should_detect_rss_settings_for_AlphaRatio()
{
_indexerSettings.AllowZeroSize = true;
GivenRecentFeedResponse("TorrentRss/AlphaRatio.xml");
var settings = Subject.Detect(_indexerSettings);
settings.Should().BeEquivalentTo(new TorrentRssIndexerParserSettings
{
UseEZTVFormat = false,
UseEnclosureUrl = false,
UseEnclosureLength = false,
ParseSizeInDescription = true,
ParseSeedersInDescription = false,
SizeElementName = null
});
}
[Test]
[Ignore("Cannot reliably reject unparseable titles")]
public void should_reject_rss_settings_for_AwesomeHD()
{
_indexerSettings.AllowZeroSize = true;
GivenRecentFeedResponse("TorrentRss/AwesomeHD.xml");
var settings = Subject.Detect(_indexerSettings);
settings.Should().BeNull();
}
[Test]
public void should_detect_rss_settings_for_AnimeTosho_without_size()
{
_indexerSettings.AllowZeroSize = true;
GivenRecentFeedResponse("TorrentRss/AnimeTosho_NoSize.xml");
var settings = Subject.Detect(_indexerSettings);
settings.Should().BeEquivalentTo(new TorrentRssIndexerParserSettings
{
UseEZTVFormat = false,
UseEnclosureUrl = true,
UseEnclosureLength = false,
ParseSizeInDescription = true,
ParseSeedersInDescription = false,
SizeElementName = null
});
}
[TestCase("IPTorrents/IPTorrents.xml")]
[TestCase("Nyaa/Nyaa.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);
var settings = Subject.Detect(_indexerSettings);
settings.Should().NotBeNull();
}
[TestCase("TorrentRss/invalid/ImmortalSeed_InvalidDownloadUrl.xml")]
public void should_reject_recent_feed_with_invalid_downloadurl(string rssXmlFile)
{
GivenRecentFeedResponse(rssXmlFile);
var ex = Assert.Throws<UnsupportedFeedException>(() => Subject.Detect(_indexerSettings));
ex.Message.Should().Contain("download url");
}
[TestCase("TorrentRss/invalid/TorrentDay_NoPubDate.xml")]
public void should_reject_recent_feed_without_pubDate(string rssXmlFile)
{
GivenRecentFeedResponse(rssXmlFile);
var ex = Assert.Throws<UnsupportedFeedException>(() => Subject.Detect(_indexerSettings));
ex.Message.Should().Contain("Rss feed must have a pubDate");
}
[TestCase("Torrentleech/Torrentleech.xml")]
[TestCase("TorrentRss/invalid/Eztv_InvalidSize.xml")]
[TestCase("TorrentRss/invalid/ImmortalSeed_InvalidSize.xml")]
[TestCase("TorrentRss/Doki.xml")]
public void should_detect_feed_without_size(string rssXmlFile)
{
_indexerSettings.AllowZeroSize = true;
GivenRecentFeedResponse(rssXmlFile);
var settings = Subject.Detect(_indexerSettings);
settings.Should().NotBeNull();
settings.UseEnclosureLength.Should().BeFalse();
settings.ParseSizeInDescription.Should().BeFalse();
settings.SizeElementName.Should().BeNull();
}
[TestCase("TorrentRss/invalid/Eztv_InvalidSize.xml")]
[TestCase("TorrentRss/invalid/ImmortalSeed_InvalidSize.xml")]
public void should_reject_feed_without_size(string rssXmlFile)
{
GivenRecentFeedResponse(rssXmlFile);
var ex = Assert.Throws<UnsupportedFeedException>(() => Subject.Detect(_indexerSettings));
ex.Message.Should().Contain("content size");
}
}
}

@ -0,0 +1,84 @@
using System.Collections.Generic;
using NLog;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration;
namespace NzbDrone.Core.Indexers.Definitions
{
public class AlphaRatio : Gazelle.Gazelle
{
public override string Name => "AlphaRatio";
public override string BaseUrl => "https://alpharatio.cc/";
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
public AlphaRatio(IHttpClient httpClient, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
: base(httpClient, indexerStatusService, configService, logger)
{
}
public override IIndexerRequestGenerator GetRequestGenerator()
{
return new AlphaRatioRequestGenerator()
{
Settings = Settings,
HttpClient = _httpClient,
Logger = _logger,
Capabilities = Capabilities,
BaseUrl = BaseUrl
};
}
protected override IndexerCapabilities SetCapabilities()
{
var caps = new IndexerCapabilities
{
TvSearchParams = new List<TvSearchParam>
{
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep
},
MovieSearchParams = new List<MovieSearchParam>
{
MovieSearchParam.Q, MovieSearchParam.ImdbId
}
};
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.TVSD, "TvSD");
caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.TVHD, "TvHD");
caps.Categories.AddCategoryMapping(3, NewznabStandardCategory.TVUHD, "TvUHD");
caps.Categories.AddCategoryMapping(4, NewznabStandardCategory.TVSD, "TvDVDRip");
caps.Categories.AddCategoryMapping(5, NewznabStandardCategory.TVSD, "TvPackSD");
caps.Categories.AddCategoryMapping(6, NewznabStandardCategory.TVHD, "TvPackHD");
caps.Categories.AddCategoryMapping(7, NewznabStandardCategory.TVUHD, "TvPackUHD");
caps.Categories.AddCategoryMapping(8, NewznabStandardCategory.MoviesSD, "MovieSD");
caps.Categories.AddCategoryMapping(9, NewznabStandardCategory.MoviesHD, "MovieHD");
caps.Categories.AddCategoryMapping(10, NewznabStandardCategory.MoviesUHD, "MovieUHD");
caps.Categories.AddCategoryMapping(11, NewznabStandardCategory.MoviesSD, "MoviePackSD");
caps.Categories.AddCategoryMapping(12, NewznabStandardCategory.MoviesHD, "MoviePackHD");
caps.Categories.AddCategoryMapping(13, NewznabStandardCategory.MoviesUHD, "MoviePackUHD");
caps.Categories.AddCategoryMapping(14, NewznabStandardCategory.XXX, "MovieXXX");
caps.Categories.AddCategoryMapping(15, NewznabStandardCategory.MoviesBluRay, "Bluray");
caps.Categories.AddCategoryMapping(16, NewznabStandardCategory.TVAnime, "AnimeSD");
caps.Categories.AddCategoryMapping(17, NewznabStandardCategory.TVAnime, "AnimeHD");
caps.Categories.AddCategoryMapping(18, NewznabStandardCategory.PCGames, "GamesPC");
caps.Categories.AddCategoryMapping(19, NewznabStandardCategory.ConsoleXBox, "GamesxBox");
caps.Categories.AddCategoryMapping(20, NewznabStandardCategory.ConsolePS4, "GamesPS");
caps.Categories.AddCategoryMapping(21, NewznabStandardCategory.ConsoleWii, "GamesNin");
caps.Categories.AddCategoryMapping(22, NewznabStandardCategory.PC0day, "AppsWindows");
caps.Categories.AddCategoryMapping(23, NewznabStandardCategory.PCMac, "AppsMAC");
caps.Categories.AddCategoryMapping(24, NewznabStandardCategory.PC0day, "AppsLinux");
caps.Categories.AddCategoryMapping(25, NewznabStandardCategory.PCMobileOther, "AppsMobile");
caps.Categories.AddCategoryMapping(26, NewznabStandardCategory.XXX, "0dayXXX");
caps.Categories.AddCategoryMapping(27, NewznabStandardCategory.Books, "eBook");
caps.Categories.AddCategoryMapping(28, NewznabStandardCategory.AudioAudiobook, "AudioBook");
caps.Categories.AddCategoryMapping(29, NewznabStandardCategory.AudioOther, "Music");
caps.Categories.AddCategoryMapping(30, NewznabStandardCategory.Other, "Misc");
return caps;
}
}
public class AlphaRatioRequestGenerator : Gazelle.GazelleRequestGenerator
{
protected override bool ImdbInTags => true;
}
}

@ -8,6 +8,7 @@ namespace NzbDrone.Core.Indexers.AwesomeHD
public class AwesomeHD : HttpIndexerBase<AwesomeHDSettings>
{
public override string Name => "AwesomeHD";
public override string BaseUrl => "https://awesome-hd.club";
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
public override bool SupportsRss => true;
@ -24,12 +25,12 @@ namespace NzbDrone.Core.Indexers.AwesomeHD
public override IIndexerRequestGenerator GetRequestGenerator()
{
return new AwesomeHDRequestGenerator() { Settings = Settings };
return new AwesomeHDRequestGenerator() { Settings = Settings, BaseUrl = BaseUrl };
}
public override IParseIndexerResponse GetParser()
{
return new AwesomeHDRssParser(Settings);
return new AwesomeHDRssParser(Settings, BaseUrl);
}
private IndexerCapabilities SetCapabilities()

@ -8,6 +8,7 @@ namespace NzbDrone.Core.Indexers.AwesomeHD
{
public class AwesomeHDRequestGenerator : IIndexerRequestGenerator
{
public string BaseUrl { get; set; }
public AwesomeHDSettings Settings { get; set; }
public virtual IndexerPageableRequestChain GetRecentRequests()
@ -66,7 +67,7 @@ namespace NzbDrone.Core.Indexers.AwesomeHD
{
if (searchParameters != null)
{
yield return new IndexerRequest($"{Settings.BaseUrl.Trim().TrimEnd('/')}/searchapi.php?passkey={Settings.Passkey.Trim()}{searchParameters}", HttpAccept.Rss);
yield return new IndexerRequest($"{BaseUrl.Trim().TrimEnd('/')}/searchapi.php?passkey={Settings.Passkey.Trim()}{searchParameters}", HttpAccept.Rss);
}
}
}

@ -12,11 +12,13 @@ namespace NzbDrone.Core.Indexers.AwesomeHD
{
public class AwesomeHDRssParser : IParseIndexerResponse
{
private readonly string _baseUrl;
private readonly AwesomeHDSettings _settings;
public AwesomeHDRssParser(AwesomeHDSettings settings)
public AwesomeHDRssParser(AwesomeHDSettings settings, string baseUrl)
{
_settings = settings;
_baseUrl = baseUrl;
}
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
@ -145,7 +147,7 @@ namespace NzbDrone.Core.Indexers.AwesomeHD
private string GetDownloadUrl(string torrentId, string authKey, string passKey)
{
var url = new HttpUri(_settings.BaseUrl)
var url = new HttpUri(_baseUrl)
.CombinePath("/torrents.php")
.AddQueryParam("action", "download")
.AddQueryParam("id", torrentId)
@ -157,7 +159,7 @@ namespace NzbDrone.Core.Indexers.AwesomeHD
private string GetInfoUrl(string groupId, string torrentId)
{
var url = new HttpUri(_settings.BaseUrl)
var url = new HttpUri(_baseUrl)
.CombinePath("/torrents.php")
.AddQueryParam("id", groupId)
.AddQueryParam("torrentid", torrentId);

@ -1,5 +1,6 @@
using FluentValidation;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Indexers.AwesomeHD
@ -8,22 +9,18 @@ namespace NzbDrone.Core.Indexers.AwesomeHD
{
public AwesomeHDSettingsValidator()
{
RuleFor(c => c.BaseUrl).ValidRootUrl();
RuleFor(c => c.Passkey).NotEmpty();
}
}
public class AwesomeHDSettings : IIndexerSettings
public class AwesomeHDSettings : IProviderConfig
{
private static readonly AwesomeHDSettingsValidator Validator = new AwesomeHDSettingsValidator();
public AwesomeHDSettings()
{
BaseUrl = "https://awesome-hd.club";
}
public string BaseUrl { get; set; }
[FieldDefinition(1, Label = "Passkey", Privacy = PrivacyLevel.ApiKey)]
public string Passkey { get; set; }

@ -0,0 +1,70 @@
using System.Collections.Generic;
using NLog;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration;
namespace NzbDrone.Core.Indexers.BroadcastheNet
{
public class BroadcastheNet : HttpIndexerBase<BroadcastheNetSettings>
{
public override string Name => "BroadcasTheNet";
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
public override bool SupportsRss => true;
public override bool SupportsSearch => true;
public override int PageSize => 100;
public override IndexerCapabilities Capabilities => SetCapabilities();
public override string BaseUrl => "http://api.broadcasthe.net/";
public BroadcastheNet(IHttpClient httpClient, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
: base(httpClient, indexerStatusService, configService, logger)
{
}
public override IIndexerRequestGenerator GetRequestGenerator()
{
var requestGenerator = new BroadcastheNetRequestGenerator() { Settings = Settings, PageSize = PageSize, BaseUrl = BaseUrl };
var releaseInfo = _indexerStatusService.GetLastRssSyncReleaseInfo(Definition.Id);
if (releaseInfo != null)
{
int torrentID;
if (int.TryParse(releaseInfo.Guid.Replace("BTN-", string.Empty), out torrentID))
{
requestGenerator.LastRecentTorrentID = torrentID;
}
}
return requestGenerator;
}
public override IParseIndexerResponse GetParser()
{
return new BroadcastheNetParser();
}
private IndexerCapabilities SetCapabilities()
{
var caps = new IndexerCapabilities
{
LimitsDefault = 100,
LimitsMax = 1000,
TvSearchParams = new List<TvSearchParam>
{
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep
}
};
caps.Categories.AddCategoryMapping("SD", NewznabStandardCategory.TVSD, "SD");
caps.Categories.AddCategoryMapping("720p", NewznabStandardCategory.TVHD, "720p");
caps.Categories.AddCategoryMapping("1080p", NewznabStandardCategory.TVHD, "1080p");
caps.Categories.AddCategoryMapping("1080i", NewznabStandardCategory.TVHD, "1080i");
caps.Categories.AddCategoryMapping("2160p", NewznabStandardCategory.TVHD, "2160p");
caps.Categories.AddCategoryMapping("Portable Device", NewznabStandardCategory.TVSD, "Portable Device");
return caps;
}
}
}

@ -0,0 +1,109 @@
using System;
using System.Collections.Generic;
using System.Net;
using System.Text.RegularExpressions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Indexers.Exceptions;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.Indexers.BroadcastheNet
{
public class BroadcastheNetParser : IParseIndexerResponse
{
private static readonly Regex RegexProtocol = new Regex("^https?:", RegexOptions.Compiled);
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
{
var results = new List<ReleaseInfo>();
switch (indexerResponse.HttpResponse.StatusCode)
{
case HttpStatusCode.Unauthorized:
throw new ApiKeyException("API Key invalid or not authorized");
case HttpStatusCode.NotFound:
throw new IndexerException(indexerResponse, "Indexer API call returned NotFound, the Indexer API may have changed.");
case HttpStatusCode.ServiceUnavailable:
throw new RequestLimitReachedException("Cannot do more than 150 API requests per hour.");
default:
if (indexerResponse.HttpResponse.StatusCode != HttpStatusCode.OK)
{
throw new IndexerException(indexerResponse, "Indexer API call returned an unexpected StatusCode [{0}]", indexerResponse.HttpResponse.StatusCode);
}
break;
}
if (indexerResponse.HttpResponse.Headers.ContentType != null && indexerResponse.HttpResponse.Headers.ContentType.Contains("text/html"))
{
throw new IndexerException(indexerResponse, "Indexer responded with html content. Site is likely blocked or unavailable.");
}
if (indexerResponse.Content == "Query execution was interrupted")
{
throw new IndexerException(indexerResponse, "Indexer API returned an internal server error");
}
JsonRpcResponse<BroadcastheNetTorrents> jsonResponse = new HttpResponse<JsonRpcResponse<BroadcastheNetTorrents>>(indexerResponse.HttpResponse).Resource;
if (jsonResponse.Error != null || jsonResponse.Result == null)
{
throw new IndexerException(indexerResponse, "Indexer API call returned an error [{0}]", jsonResponse.Error);
}
if (jsonResponse.Result.Results == 0)
{
return results;
}
var protocol = indexerResponse.HttpRequest.Url.Scheme + ":";
foreach (var torrent in jsonResponse.Result.Torrents.Values)
{
var torrentInfo = new TorrentInfo();
torrentInfo.Guid = string.Format("BTN-{0}", torrent.TorrentID);
torrentInfo.Title = CleanReleaseName(torrent.ReleaseName);
torrentInfo.Size = torrent.Size;
torrentInfo.DownloadUrl = RegexProtocol.Replace(torrent.DownloadURL, protocol);
torrentInfo.InfoUrl = string.Format("{0}//broadcasthe.net/torrents.php?id={1}&torrentid={2}", protocol, torrent.GroupID, torrent.TorrentID);
//torrentInfo.CommentUrl =
if (torrent.TvdbID.HasValue)
{
torrentInfo.TvdbId = torrent.TvdbID.Value;
}
if (torrent.TvrageID.HasValue)
{
torrentInfo.TvRageId = torrent.TvrageID.Value;
}
torrentInfo.PublishDate = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc).ToUniversalTime().AddSeconds(torrent.Time);
//torrentInfo.MagnetUrl =
torrentInfo.InfoHash = torrent.InfoHash;
torrentInfo.Seeders = torrent.Seeders;
torrentInfo.Peers = torrent.Leechers + torrent.Seeders;
torrentInfo.Origin = torrent.Origin;
torrentInfo.Source = torrent.Source;
torrentInfo.Container = torrent.Container;
torrentInfo.Codec = torrent.Codec;
torrentInfo.Resolution = torrent.Resolution;
results.Add(torrentInfo);
}
return results;
}
private string CleanReleaseName(string releaseName)
{
releaseName = releaseName.Replace("\\", "");
return releaseName;
}
}
}

@ -0,0 +1,118 @@
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Common.Http;
using NzbDrone.Core.IndexerSearch.Definitions;
namespace NzbDrone.Core.Indexers.BroadcastheNet
{
public class BroadcastheNetRequestGenerator : IIndexerRequestGenerator
{
public int MaxPages { get; set; }
public int PageSize { get; set; }
public BroadcastheNetSettings Settings { get; set; }
public IndexerCapabilities Capabilities { get; set; }
public int? LastRecentTorrentID { get; set; }
public System.Func<IDictionary<string, string>> GetCookies { get => throw new System.NotImplementedException(); set => throw new System.NotImplementedException(); }
public System.Action<IDictionary<string, string>, System.DateTime?> CookiesUpdater { get => throw new System.NotImplementedException(); set => throw new System.NotImplementedException(); }
public string BaseUrl { get; set; }
public BroadcastheNetRequestGenerator()
{
MaxPages = 10;
PageSize = 100;
}
private IEnumerable<IndexerRequest> GetPagedRequests(BroadcastheNetTorrentQuery parameters, int results, int offset)
{
var builder = new JsonRpcRequestBuilder(BaseUrl)
.Call("getTorrents", Settings.ApiKey, parameters, results, offset);
builder.SuppressHttpError = true;
yield return new IndexerRequest(builder.Build());
}
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
{
return new IndexerPageableRequestChain();
}
public IndexerPageableRequestChain GetSearchRequests(MusicSearchCriteria searchCriteria)
{
return new IndexerPageableRequestChain();
}
public IndexerPageableRequestChain GetSearchRequests(TvSearchCriteria searchCriteria)
{
var pageableRequests = new IndexerPageableRequestChain();
var parameters = new BroadcastheNetTorrentQuery();
var searchString = searchCriteria.SearchTerm != null ? searchCriteria.SearchTerm : "";
var btnResults = searchCriteria.Limit.GetValueOrDefault();
if (btnResults == 0)
{
btnResults = (int)Capabilities.LimitsDefault;
}
var btnOffset = searchCriteria.Offset.GetValueOrDefault();
if (searchCriteria.TvdbId != 0)
{
parameters.Tvdb = string.Format("{0}", searchCriteria.TvdbId);
}
if (searchCriteria.RId != 0)
{
parameters.Tvrage = string.Format("{0}", searchCriteria.RId);
}
// If only the season/episode is searched for then change format to match expected format
if (searchCriteria.Season > 0 && searchCriteria.Ep == null)
{
parameters.Name = string.Format("Season {0}%", searchCriteria.Season.Value);
parameters.Category = "Season";
}
else if (searchCriteria.Season > 0 && searchCriteria.Ep.Value > 0)
{
parameters.Name = string.Format("S{0:00}E{1:00}", searchCriteria.Season.Value, searchCriteria.Ep.Value);
parameters.Category = "Episode";
}
parameters.Search = searchString.Replace(" ", "%");
pageableRequests.Add(GetPagedRequests(parameters, btnResults, btnOffset));
return pageableRequests;
}
public IndexerPageableRequestChain GetSearchRequests(BookSearchCriteria searchCriteria)
{
return new IndexerPageableRequestChain();
}
public IndexerPageableRequestChain GetSearchRequests(BasicSearchCriteria searchCriteria)
{
var pageableRequests = new IndexerPageableRequestChain();
var parameters = new BroadcastheNetTorrentQuery();
var searchString = searchCriteria.SearchTerm != null ? searchCriteria.SearchTerm : "";
var btnResults = searchCriteria.Limit.GetValueOrDefault();
if (btnResults == 0)
{
btnResults = (int)Capabilities.LimitsDefault;
}
parameters.Search = searchString.Replace(" ", "%");
var btnOffset = searchCriteria.Offset.GetValueOrDefault();
pageableRequests.Add(GetPagedRequests(parameters, btnResults, btnOffset));
return pageableRequests;
}
}
}

@ -0,0 +1,32 @@
using FluentValidation;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Indexers.BroadcastheNet
{
public class BroadcastheNetSettingsValidator : AbstractValidator<BroadcastheNetSettings>
{
public BroadcastheNetSettingsValidator()
{
RuleFor(c => c.ApiKey).NotEmpty();
}
}
public class BroadcastheNetSettings : IProviderConfig
{
private static readonly BroadcastheNetSettingsValidator Validator = new BroadcastheNetSettingsValidator();
public BroadcastheNetSettings()
{
}
[FieldDefinition(1, Label = "API Key", Privacy = PrivacyLevel.ApiKey)]
public string ApiKey { get; set; }
public NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));
}
}
}

@ -0,0 +1,31 @@
namespace NzbDrone.Core.Indexers.BroadcastheNet
{
public class BroadcastheNetTorrent
{
public string GroupName { get; set; }
public int GroupID { get; set; }
public int TorrentID { get; set; }
public int SeriesID { get; set; }
public string Series { get; set; }
public string SeriesBanner { get; set; }
public string SeriesPoster { get; set; }
public string YoutubeTrailer { get; set; }
public string Category { get; set; }
public int? Snatched { get; set; }
public int? Seeders { get; set; }
public int? Leechers { get; set; }
public string Source { get; set; }
public string Container { get; set; }
public string Codec { get; set; }
public string Resolution { get; set; }
public string Origin { get; set; }
public string ReleaseName { get; set; }
public long Size { get; set; }
public long Time { get; set; }
public int? TvdbID { get; set; }
public int? TvrageID { get; set; }
public string ImdbID { get; set; }
public string InfoHash { get; set; }
public string DownloadURL { get; set; }
}
}

@ -0,0 +1,39 @@
using Newtonsoft.Json;
namespace NzbDrone.Core.Indexers.BroadcastheNet
{
public class BroadcastheNetTorrentQuery
{
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
public string Id { get; set; }
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
public string Category { get; set; }
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
public string Name { get; set; }
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
public string Search { get; set; }
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
public string Codec { get; set; }
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
public string Container { get; set; }
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
public string Source { get; set; }
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
public string Resolution { get; set; }
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
public string Origin { get; set; }
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
public string Hash { get; set; }
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
public string Tvdb { get; set; }
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
public string Tvrage { get; set; }
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
public string Age { get; set; }
public BroadcastheNetTorrentQuery Clone()
{
return MemberwiseClone() as BroadcastheNetTorrentQuery;
}
}
}

@ -0,0 +1,10 @@
using System.Collections.Generic;
namespace NzbDrone.Core.Indexers.BroadcastheNet
{
public class BroadcastheNetTorrents
{
public Dictionary<int, BroadcastheNetTorrent> Torrents { get; set; }
public int Results { get; set; }
}
}

@ -15,6 +15,7 @@ namespace NzbDrone.Core.Indexers.Cardigann
private readonly IIndexerDefinitionUpdateService _definitionService;
public override string Name => "Cardigann";
public override string BaseUrl => throw new System.NotImplementedException();
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;

@ -2,7 +2,7 @@ using System.Collections.Generic;
using FluentValidation;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.Languages;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Indexers.Cardigann
@ -14,7 +14,7 @@ namespace NzbDrone.Core.Indexers.Cardigann
}
}
public class CardigannSettings : IIndexerSettings
public class CardigannSettings : IProviderConfig
{
private static readonly CardigannSettingsValidator Validator = new CardigannSettingsValidator();
@ -28,8 +28,6 @@ namespace NzbDrone.Core.Indexers.Cardigann
public Dictionary<string, object> ExtraFieldData { get; set; }
public string BaseUrl { get; set; }
// Field 8 is used by TorznabSettings MinimumSeeders
// If you need to add another field here, update TorznabSettings as well and this comment
public virtual NzbDroneValidationResult Validate()

@ -12,6 +12,7 @@ using NzbDrone.Core.Configuration;
using NzbDrone.Core.Indexers.Exceptions;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Indexers.Definitions
@ -19,6 +20,7 @@ namespace NzbDrone.Core.Indexers.Definitions
public class DigitalCore : HttpIndexerBase<DigitalCoreSettings>
{
public override string Name => "DigitalCore";
public override string BaseUrl => "https://digitalcore.club/";
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
public override IndexerCapabilities Capabilities => SetCapabilities();
@ -35,7 +37,7 @@ namespace NzbDrone.Core.Indexers.Definitions
public override IParseIndexerResponse GetParser()
{
return new DigitalCoreParser(Settings, Capabilities.Categories);
return new DigitalCoreParser(Settings, Capabilities.Categories, BaseUrl);
}
private IndexerCapabilities SetCapabilities()
@ -107,6 +109,7 @@ namespace NzbDrone.Core.Indexers.Definitions
public class DigitalCoreRequestGenerator : IIndexerRequestGenerator
{
public string BaseUrl { get; set; }
public DigitalCoreSettings Settings { get; set; }
public IndexerCapabilities Capabilities { get; set; }
@ -121,7 +124,7 @@ namespace NzbDrone.Core.Indexers.Definitions
private IEnumerable<IndexerRequest> GetPagedRequests(string term, int[] categories)
{
var baseUrl = string.Format("{0}/api/v1/torrents?extendedSearch=false", Settings.BaseUrl.TrimEnd('/'));
var baseUrl = string.Format("{0}/api/v1/torrents?extendedSearch=false", BaseUrl.TrimEnd('/'));
var parameters = string.Empty;
@ -190,13 +193,15 @@ namespace NzbDrone.Core.Indexers.Definitions
public class DigitalCoreParser : IParseIndexerResponse
{
private readonly string _baseUrl;
private readonly DigitalCoreSettings _settings;
private readonly IndexerCapabilitiesCategories _categories;
public DigitalCoreParser(DigitalCoreSettings settings, IndexerCapabilitiesCategories categories)
public DigitalCoreParser(DigitalCoreSettings settings, IndexerCapabilitiesCategories categories, string baseUrl)
{
_settings = settings;
_categories = categories;
_baseUrl = baseUrl;
}
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
@ -230,8 +235,8 @@ namespace NzbDrone.Core.Indexers.Definitions
release.Files = row.numfiles;
release.Grabs = row.times_completed;
release.Guid = new Uri(_settings.BaseUrl + "torrent/" + row.id.ToString() + "/").ToString();
release.DownloadUrl = _settings.BaseUrl + "api/v1/torrents/download/" + row.id.ToString();
release.Guid = new Uri(_baseUrl + "torrent/" + row.id.ToString() + "/").ToString();
release.DownloadUrl = _baseUrl + "api/v1/torrents/download/" + row.id.ToString();
if (row.imdbid2 != null && row.imdbid2.ToString().StartsWith("tt"))
{
@ -266,19 +271,16 @@ namespace NzbDrone.Core.Indexers.Definitions
}
}
public class DigitalCoreSettings : IIndexerSettings
public class DigitalCoreSettings : IProviderConfig
{
private static readonly DigitalCoreSettingsValidator Validator = new DigitalCoreSettingsValidator();
public DigitalCoreSettings()
{
BaseUrl = "https://digitalcore.club/";
UId = "";
Passphrase = "";
}
public string BaseUrl { get; set; }
[FieldDefinition(1, Label = "UID", Advanced = true, HelpText = "Uid from login cookie")]
public string UId { get; set; }

@ -7,6 +7,7 @@ namespace NzbDrone.Core.Indexers.FileList
public class FileList : HttpIndexerBase<FileListSettings>
{
public override string Name => "FileList.io";
public override string BaseUrl => "https://filelist.io";
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
public override bool SupportsRss => true;
@ -19,12 +20,12 @@ namespace NzbDrone.Core.Indexers.FileList
public override IIndexerRequestGenerator GetRequestGenerator()
{
return new FileListRequestGenerator() { Settings = Settings };
return new FileListRequestGenerator() { Settings = Settings, BaseUrl = BaseUrl };
}
public override IParseIndexerResponse GetParser()
{
return new FileListParser(Settings);
return new FileListParser(Settings, BaseUrl);
}
}
}

@ -10,11 +10,13 @@ namespace NzbDrone.Core.Indexers.FileList
{
public class FileListParser : IParseIndexerResponse
{
private readonly string _baseUrl;
private readonly FileListSettings _settings;
public FileListParser(FileListSettings settings)
public FileListParser(FileListSettings settings, string baseUrl)
{
_settings = settings;
_baseUrl = baseUrl;
}
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
@ -69,7 +71,7 @@ namespace NzbDrone.Core.Indexers.FileList
private string GetDownloadUrl(string torrentId)
{
var url = new HttpUri(_settings.BaseUrl)
var url = new HttpUri(_baseUrl)
.CombinePath("/download.php")
.AddQueryParam("id", torrentId)
.AddQueryParam("passkey", _settings.Passkey);
@ -79,7 +81,7 @@ namespace NzbDrone.Core.Indexers.FileList
private string GetInfoUrl(string torrentId)
{
var url = new HttpUri(_settings.BaseUrl)
var url = new HttpUri(_baseUrl)
.CombinePath("/details.php")
.AddQueryParam("id", torrentId);

@ -9,6 +9,7 @@ namespace NzbDrone.Core.Indexers.FileList
{
public class FileListRequestGenerator : IIndexerRequestGenerator
{
public string BaseUrl { get; set; }
public FileListSettings Settings { get; set; }
public Func<IDictionary<string, string>> GetCookies { get; set; }
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
@ -58,7 +59,7 @@ namespace NzbDrone.Core.Indexers.FileList
{
var categoriesQuery = string.Join(",", categories.Distinct());
var baseUrl = string.Format("{0}/api.php?action={1}&category={2}&username={3}&passkey={4}{5}", Settings.BaseUrl.TrimEnd('/'), searchType, categoriesQuery, Settings.Username.Trim(), Settings.Passkey.Trim(), parameters);
var baseUrl = string.Format("{0}/api.php?action={1}&category={2}&username={3}&passkey={4}{5}", BaseUrl.TrimEnd('/'), searchType, categoriesQuery, Settings.Username.Trim(), Settings.Passkey.Trim(), parameters);
yield return new IndexerRequest(baseUrl, HttpAccept.Json);
}

@ -3,6 +3,7 @@ using FluentValidation;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.Languages;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Indexers.FileList
@ -11,23 +12,19 @@ namespace NzbDrone.Core.Indexers.FileList
{
public FileListSettingsValidator()
{
RuleFor(c => c.BaseUrl).ValidRootUrl();
RuleFor(c => c.Username).NotEmpty();
RuleFor(c => c.Passkey).NotEmpty();
}
}
public class FileListSettings : IIndexerSettings
public class FileListSettings : IProviderConfig
{
private static readonly FileListSettingsValidator Validator = new FileListSettingsValidator();
public FileListSettings()
{
BaseUrl = "https://filelist.io";
}
public string BaseUrl { get; set; }
[FieldDefinition(0, Label = "Username", Privacy = PrivacyLevel.UserName)]
public string Username { get; set; }

@ -0,0 +1,48 @@
using NLog;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration;
namespace NzbDrone.Core.Indexers.Gazelle
{
public abstract class Gazelle : HttpIndexerBase<GazelleSettings>
{
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
public override string BaseUrl => "";
public override bool SupportsRss => true;
public override bool SupportsSearch => true;
public override int PageSize => 50;
public override IndexerCapabilities Capabilities => SetCapabilities();
public Gazelle(IHttpClient httpClient,
IIndexerStatusService indexerStatusService,
IConfigService configService,
Logger logger)
: base(httpClient, indexerStatusService, configService, logger)
{
}
public override IIndexerRequestGenerator GetRequestGenerator()
{
return new GazelleRequestGenerator()
{
Settings = Settings,
HttpClient = _httpClient,
Logger = _logger,
Capabilities = Capabilities,
BaseUrl = BaseUrl
};
}
public override IParseIndexerResponse GetParser()
{
return new GazelleParser(Settings, Capabilities, BaseUrl);
}
protected virtual IndexerCapabilities SetCapabilities()
{
var caps = new IndexerCapabilities();
return caps;
}
}
}

@ -0,0 +1,85 @@
using System;
using System.Collections.Generic;
namespace NzbDrone.Core.Indexers.Gazelle
{
public class GazelleArtist
{
public string Name { get; set; }
public string Id { get; set; }
public string Aliasid { get; set; }
}
public class GazelleTorrent
{
public int TorrentId { get; set; }
public int EditionId { get; set; }
public List<GazelleArtist> Artists { get; set; }
public bool Remastered { get; set; }
public string RemasterYear { get; set; }
public string RemasterTitle { get; set; }
public string Media { get; set; }
public string Encoding { get; set; }
public string Format { get; set; }
public bool HasLog { get; set; }
public int LogScore { get; set; }
public bool HasCue { get; set; }
public bool Scene { get; set; }
public bool VanityHouse { get; set; }
public int FileCount { get; set; }
public DateTime Time { get; set; }
public string Size { get; set; }
public string Snatches { get; set; }
public string Seeders { get; set; }
public string Leechers { get; set; }
public string Category { get; set; }
public bool IsFreeLeech { get; set; }
public bool IsNeutralLeech { get; set; }
public bool IsPersonalFreeLeech { get; set; }
public bool CanUseToken { get; set; }
}
public class GazelleRelease
{
public string GroupId { get; set; }
public string GroupName { get; set; }
public string Artist { get; set; }
public string GroupYear { get; set; }
public string Cover { get; set; }
public List<string> Tags { get; set; }
public string ReleaseType { get; set; }
public int TotalLeechers { get; set; }
public int TotalSeeders { get; set; }
public int TotalSnatched { get; set; }
public long MaxSize { get; set; }
public string GroupTime { get; set; }
public List<GazelleTorrent> Torrents { get; set; }
}
public class GazelleResponse
{
public string Status { get; set; }
public GazelleBrowseResponse Response { get; set; }
}
public class GazelleBrowseResponse
{
public List<GazelleRelease> Results { get; set; }
public string CurrentPage { get; set; }
public string Pages { get; set; }
}
public class GazelleAuthResponse
{
public string Status { get; set; }
public GazelleIndexResponse Response { get; set; }
}
public class GazelleIndexResponse
{
public string Username { get; set; }
public string Id { get; set; }
public string Authkey { get; set; }
public string Passkey { get; set; }
}
}

@ -0,0 +1,9 @@
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.Indexers.Gazelle
{
public class GazelleInfo : TorrentInfo
{
public bool? Scene { get; set; }
}
}

@ -0,0 +1,130 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Indexers.Exceptions;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.Indexers.Gazelle
{
public class GazelleParser : IParseIndexerResponse
{
private readonly GazelleSettings _settings;
private readonly IndexerCapabilities _capabilities;
private readonly string _baseUrl;
public GazelleParser(GazelleSettings settings, IndexerCapabilities capabilities, string baseUrl)
{
_settings = settings;
_capabilities = capabilities;
_baseUrl = baseUrl;
}
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
{
var torrentInfos = new List<ReleaseInfo>();
if (indexerResponse.HttpResponse.StatusCode != HttpStatusCode.OK)
{
// Remove cookie cache
CookiesUpdater(null, null);
throw new IndexerException(indexerResponse, $"Unexpected response status {indexerResponse.HttpResponse.StatusCode} code from API request");
}
if (!indexerResponse.HttpResponse.Headers.ContentType.Contains(HttpAccept.Json.Value))
{
// Remove cookie cache
CookiesUpdater(null, null);
throw new IndexerException(indexerResponse, $"Unexpected response header {indexerResponse.HttpResponse.Headers.ContentType} from API request, expected {HttpAccept.Json.Value}");
}
var jsonResponse = new HttpResponse<GazelleResponse>(indexerResponse.HttpResponse);
if (jsonResponse.Resource.Status != "success" ||
jsonResponse.Resource.Status.IsNullOrWhiteSpace() ||
jsonResponse.Resource.Response == null)
{
return torrentInfos;
}
foreach (var result in jsonResponse.Resource.Response.Results)
{
if (result.Torrents != null)
{
foreach (var torrent in result.Torrents)
{
var id = torrent.TorrentId;
var artist = WebUtility.HtmlDecode(result.Artist);
var album = WebUtility.HtmlDecode(result.GroupName);
var title = $"{result.Artist} - {result.GroupName} ({result.GroupYear}) [{torrent.Format} {torrent.Encoding}] [{torrent.Media}]";
if (torrent.HasCue)
{
title += " [Cue]";
}
var release = new GazelleInfo()
{
Guid = string.Format("Gazelle-{0}", id),
Title = WebUtility.HtmlDecode(title),
Container = torrent.Encoding,
Codec = torrent.Format,
Size = long.Parse(torrent.Size),
DownloadUrl = GetDownloadUrl(id),
InfoUrl = GetInfoUrl(result.GroupId, id),
Seeders = int.Parse(torrent.Seeders),
Peers = int.Parse(torrent.Leechers) + int.Parse(torrent.Seeders),
PublishDate = torrent.Time.ToUniversalTime(),
Scene = torrent.Scene,
};
var category = torrent.Category;
if (category == null || category.Contains("Select Category"))
{
release.Category = _capabilities.Categories.MapTrackerCatToNewznab("1");
}
else
{
release.Category = _capabilities.Categories.MapTrackerCatDescToNewznab(category);
}
torrentInfos.Add(release);
}
}
}
// order by date
return
torrentInfos
.OrderByDescending(o => o.PublishDate)
.ToArray();
}
private string GetDownloadUrl(int torrentId)
{
var url = new HttpUri(_baseUrl)
.CombinePath("/torrents.php")
.AddQueryParam("action", "download")
.AddQueryParam("id", torrentId)
.AddQueryParam("authkey", _settings.AuthKey)
.AddQueryParam("torrent_pass", _settings.PassKey);
return url.FullUri;
}
private string GetInfoUrl(string groupId, int torrentId)
{
var url = new HttpUri(_baseUrl)
.CombinePath("/torrents.php")
.AddQueryParam("id", groupId)
.AddQueryParam("torrentid", torrentId);
return url.FullUri;
}
}
}

@ -0,0 +1,239 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using NLog;
using NzbDrone.Common.Cache;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.IndexerSearch.Definitions;
namespace NzbDrone.Core.Indexers.Gazelle
{
public class GazelleRequestGenerator : IIndexerRequestGenerator
{
public GazelleSettings Settings { get; set; }
public string BaseUrl { get; set; }
public IDictionary<string, string> AuthCookieCache { get; set; }
public IHttpClient HttpClient { get; set; }
public IndexerCapabilities Capabilities { get; set; }
public Logger Logger { get; set; }
protected virtual string LoginUrl => BaseUrl + "login.php";
protected virtual string APIUrl => BaseUrl + "ajax.php";
protected virtual string DownloadUrl => BaseUrl + "torrents.php?action=download&usetoken=" + (Settings.UseFreeleechToken ? "1" : "0") + "&id=";
protected virtual string DetailsUrl => BaseUrl + "torrents.php?torrentid=";
protected virtual bool ImdbInTags => false;
public Func<IDictionary<string, string>> GetCookies { get; set; }
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
public virtual IndexerPageableRequestChain GetRecentRequests()
{
var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetRequest(null));
return pageableRequests;
}
private IEnumerable<IndexerRequest> GetRequest(string searchParameters)
{
AuthCookieCache = GetCookies();
Authenticate();
var filter = "";
if (searchParameters == null)
{
}
var request =
new IndexerRequest(
$"{APIUrl}?action=browse{searchParameters}{filter}",
HttpAccept.Json);
var cookies = AuthCookieCache;
foreach (var cookie in cookies)
{
request.HttpRequest.Cookies[cookie.Key] = cookie.Value;
}
yield return request;
}
private GazelleAuthResponse GetIndex(IDictionary<string, string> cookies)
{
var indexRequestBuilder = new HttpRequestBuilder($"{APIUrl}?action=index")
{
LogResponseContent = true
};
indexRequestBuilder.SetCookies(cookies);
indexRequestBuilder.Method = HttpMethod.POST;
var authIndexRequest = indexRequestBuilder
.Accept(HttpAccept.Json)
.Build();
var indexResponse = HttpClient.Execute(authIndexRequest);
var result = Json.Deserialize<GazelleAuthResponse>(indexResponse.Content);
return result;
}
private void Authenticate()
{
var requestBuilder = new HttpRequestBuilder(LoginUrl)
{
LogResponseContent = true
};
requestBuilder.Method = HttpMethod.POST;
requestBuilder.PostProcess += r => r.RequestTimeout = TimeSpan.FromSeconds(15);
var cookies = AuthCookieCache;
if (cookies == null)
{
AuthCookieCache = null;
var authLoginRequest = requestBuilder
.AddFormParameter("username", Settings.Username)
.AddFormParameter("password", Settings.Password)
.AddFormParameter("keeplogged", "1")
.SetHeader("Content-Type", "multipart/form-data")
.Accept(HttpAccept.Json)
.Build();
var response = HttpClient.Execute(authLoginRequest);
cookies = response.GetCookies();
AuthCookieCache = cookies;
CookiesUpdater(cookies, DateTime.Now + TimeSpan.FromDays(30));
}
var index = GetIndex(cookies);
if (index == null || index.Status.IsNullOrWhiteSpace() || index.Status != "success")
{
Logger.Debug("Gazelle authentication failed.");
AuthCookieCache = null;
CookiesUpdater(null, null);
throw new Exception("Failed to authenticate with Gazelle.");
}
Logger.Debug("Gazelle authentication succeeded.");
Settings.AuthKey = index.Response.Authkey;
Settings.PassKey = index.Response.Passkey;
}
private string GetBasicSearchParameters(string searchTerm, int[] categories)
{
var searchString = GetSearchTerm(searchTerm);
var parameters = "&action=browse&order_by=time&order_way=desc";
if (!string.IsNullOrWhiteSpace(searchString))
{
parameters += string.Format("&searchstr={0}", searchString);
}
if (categories != null)
{
foreach (var cat in Capabilities.Categories.MapTorznabCapsToTrackers(categories))
{
parameters += string.Format("&filter_cat[{0}]=1", cat);
}
}
return parameters;
}
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
{
var parameters = GetBasicSearchParameters(searchCriteria.SearchTerm, searchCriteria.Categories);
if (searchCriteria.ImdbId != null)
{
if (ImdbInTags)
{
parameters += string.Format("&taglist={0}", searchCriteria.ImdbId);
}
else
{
parameters += string.Format("&cataloguenumber={0}", searchCriteria.ImdbId);
}
}
var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetRequest(parameters));
return pageableRequests;
}
public IndexerPageableRequestChain GetSearchRequests(MusicSearchCriteria searchCriteria)
{
var parameters = GetBasicSearchParameters(searchCriteria.SearchTerm, searchCriteria.Categories);
if (searchCriteria.Artist != null)
{
parameters += string.Format("&artistname={0}", searchCriteria.Artist);
}
if (searchCriteria.Label != null)
{
parameters += string.Format("&recordlabel={0}", searchCriteria.Label);
}
if (searchCriteria.Album != null)
{
parameters += string.Format("&groupname={0}", searchCriteria.Album);
}
var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetRequest(parameters));
return pageableRequests;
}
public IndexerPageableRequestChain GetSearchRequests(TvSearchCriteria searchCriteria)
{
var parameters = GetBasicSearchParameters(searchCriteria.SearchTerm, searchCriteria.Categories);
if (searchCriteria.ImdbId != null)
{
if (ImdbInTags)
{
parameters += string.Format("&taglist={0}", searchCriteria.ImdbId);
}
else
{
parameters += string.Format("&cataloguenumber={0}", searchCriteria.ImdbId);
}
}
var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetRequest(parameters));
return pageableRequests;
}
public IndexerPageableRequestChain GetSearchRequests(BookSearchCriteria searchCriteria)
{
throw new NotImplementedException();
}
// hook to adjust the search term
protected virtual string GetSearchTerm(string term) => term;
public IndexerPageableRequestChain GetSearchRequests(BasicSearchCriteria searchCriteria)
{
var parameters = GetBasicSearchParameters(searchCriteria.SearchTerm, searchCriteria.Categories);
var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetRequest(parameters));
return pageableRequests;
}
}
}

@ -0,0 +1,42 @@
using FluentValidation;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Indexers.Gazelle
{
public class GazelleSettingsValidator : AbstractValidator<GazelleSettings>
{
public GazelleSettingsValidator()
{
RuleFor(c => c.Username).NotEmpty();
RuleFor(c => c.Password).NotEmpty();
}
}
public class GazelleSettings : IProviderConfig
{
private static readonly GazelleSettingsValidator Validator = new GazelleSettingsValidator();
public GazelleSettings()
{
}
public string AuthKey;
public string PassKey;
[FieldDefinition(1, Label = "Username", HelpText = "Username", Privacy = PrivacyLevel.UserName)]
public string Username { get; set; }
[FieldDefinition(2, Label = "Password", Type = FieldType.Password, HelpText = "Password", Privacy = PrivacyLevel.Password)]
public string Password { get; set; }
[FieldDefinition(3, Type = FieldType.Checkbox, Label = "Use Freeleech Token", HelpText = "Use Freeleach Token")]
public bool UseFreeleechToken { get; set; }
public NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));
}
}
}

@ -7,6 +7,7 @@ namespace NzbDrone.Core.Indexers.HDBits
public class HDBits : HttpIndexerBase<HDBitsSettings>
{
public override string Name => "HDBits";
public override string BaseUrl => "https://hdbits.org";
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
@ -19,12 +20,12 @@ namespace NzbDrone.Core.Indexers.HDBits
public override IIndexerRequestGenerator GetRequestGenerator()
{
return new HDBitsRequestGenerator() { Settings = Settings };
return new HDBitsRequestGenerator() { Settings = Settings, BaseUrl = BaseUrl };
}
public override IParseIndexerResponse GetParser()
{
return new HDBitsParser(Settings);
return new HDBitsParser(Settings, BaseUrl);
}
}
}

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Net;
using Newtonsoft.Json;
@ -11,11 +11,13 @@ namespace NzbDrone.Core.Indexers.HDBits
{
public class HDBitsParser : IParseIndexerResponse
{
private readonly string _baseUrl;
private readonly HDBitsSettings _settings;
public HDBitsParser(HDBitsSettings settings)
public HDBitsParser(HDBitsSettings settings, string baseUrl)
{
_settings = settings;
_baseUrl = baseUrl;
}
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
@ -89,7 +91,7 @@ namespace NzbDrone.Core.Indexers.HDBits
private string GetDownloadUrl(string torrentId)
{
var url = new HttpUri(_settings.BaseUrl)
var url = new HttpUri(_baseUrl)
.CombinePath("/download.php")
.AddQueryParam("id", torrentId)
.AddQueryParam("passkey", _settings.ApiKey);
@ -99,7 +101,7 @@ namespace NzbDrone.Core.Indexers.HDBits
private string GetInfoUrl(string torrentId)
{
var url = new HttpUri(_settings.BaseUrl)
var url = new HttpUri(_baseUrl)
.CombinePath("/details.php")
.AddQueryParam("id", torrentId);

@ -11,6 +11,7 @@ namespace NzbDrone.Core.Indexers.HDBits
public class HDBitsRequestGenerator : IIndexerRequestGenerator
{
public HDBitsSettings Settings { get; set; }
public string BaseUrl { get; set; }
public virtual IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
{
@ -52,7 +53,7 @@ namespace NzbDrone.Core.Indexers.HDBits
private IEnumerable<IndexerRequest> GetRequest(TorrentQuery query)
{
var request = new HttpRequestBuilder(Settings.BaseUrl)
var request = new HttpRequestBuilder(BaseUrl)
.Resource("/api/torrents")
.Build();

@ -1,8 +1,7 @@
using System.Collections.Generic;
using FluentValidation;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.Languages;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Indexers.HDBits
@ -11,18 +10,16 @@ namespace NzbDrone.Core.Indexers.HDBits
{
public HDBitsSettingsValidator()
{
RuleFor(c => c.BaseUrl).ValidRootUrl();
RuleFor(c => c.ApiKey).NotEmpty();
}
}
public class HDBitsSettings : IIndexerSettings
public class HDBitsSettings : IProviderConfig
{
private static readonly HDBitsSettingsValidator Validator = new HDBitsSettingsValidator();
public HDBitsSettings()
{
BaseUrl = "https://hdbits.org";
Codecs = System.Array.Empty<int>();
Mediums = System.Array.Empty<int>();
}
@ -33,9 +30,6 @@ namespace NzbDrone.Core.Indexers.HDBits
[FieldDefinition(2, Label = "API Key", Privacy = PrivacyLevel.ApiKey)]
public string ApiKey { get; set; }
[FieldDefinition(3, Label = "API URL", Advanced = true, HelpText = "Do not change this unless you know what you're doing. Since your API key will be sent to that host.")]
public string BaseUrl { get; set; }
[FieldDefinition(5, Label = "Codecs", Type = FieldType.TagSelect, SelectOptions = typeof(HdBitsCodec), Advanced = true, HelpText = "Options: h264, Mpeg2, VC1, Xvid. If unspecified, all options are used.")]
public IEnumerable<int> Codecs { get; set; }

@ -1,32 +0,0 @@
using NLog;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration;
namespace NzbDrone.Core.Indexers.IPTorrents
{
public class IPTorrents : HttpIndexerBase<IPTorrentsSettings>
{
public override string Name => "IP Torrents";
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
public override bool SupportsSearch => false;
public override int PageSize => 0;
public IPTorrents(IHttpClient httpClient, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
: base(httpClient, indexerStatusService, configService, logger)
{
}
public override IIndexerRequestGenerator GetRequestGenerator()
{
return new IPTorrentsRequestGenerator() { Settings = Settings };
}
public override IParseIndexerResponse GetParser()
{
return new TorrentRssParser() { ParseSizeInDescription = true };
}
}
}

@ -1,53 +0,0 @@
using System;
using System.Collections.Generic;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.IndexerSearch.Definitions;
namespace NzbDrone.Core.Indexers.IPTorrents
{
public class IPTorrentsRequestGenerator : IIndexerRequestGenerator
{
public IPTorrentsSettings Settings { get; set; }
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
{
var pageableRequests = new IndexerPageableRequestChain();
if (searchCriteria.SearchTerm.IsNullOrWhiteSpace())
{
pageableRequests.Add(GetRssRequests());
}
return pageableRequests;
}
private IEnumerable<IndexerRequest> GetRssRequests()
{
yield return new IndexerRequest(Settings.BaseUrl, HttpAccept.Rss);
}
public IndexerPageableRequestChain GetSearchRequests(MusicSearchCriteria searchCriteria)
{
return new IndexerPageableRequestChain();
}
public IndexerPageableRequestChain GetSearchRequests(TvSearchCriteria searchCriteria)
{
return new IndexerPageableRequestChain();
}
public IndexerPageableRequestChain GetSearchRequests(BookSearchCriteria searchCriteria)
{
return new IndexerPageableRequestChain();
}
public IndexerPageableRequestChain GetSearchRequests(BasicSearchCriteria searchCriteria)
{
return new IndexerPageableRequestChain();
}
public Func<IDictionary<string, string>> GetCookies { get; set; }
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
}
}

@ -1,43 +0,0 @@
using System.Collections.Generic;
using System.Text.RegularExpressions;
using FluentValidation;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.Languages;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Indexers.IPTorrents
{
public class IPTorrentsSettingsValidator : AbstractValidator<IPTorrentsSettings>
{
public IPTorrentsSettingsValidator()
{
RuleFor(c => c.BaseUrl).ValidRootUrl();
RuleFor(c => c.BaseUrl).Matches(@"(?:/|t\.)rss\?.+$");
RuleFor(c => c.BaseUrl).Matches(@"(?:/|t\.)rss\?.+;download(?:;|$)")
.WithMessage("Use Direct Download Url (;download)")
.When(v => v.BaseUrl.IsNotNullOrWhiteSpace() && Regex.IsMatch(v.BaseUrl, @"(?:/|t\.)rss\?.+$"));
}
}
public class IPTorrentsSettings : IIndexerSettings
{
private static readonly IPTorrentsSettingsValidator Validator = new IPTorrentsSettingsValidator();
public IPTorrentsSettings()
{
BaseUrl = string.Empty;
}
[FieldDefinition(0, Label = "Feed URL", HelpText = "The full RSS feed url generated by IPTorrents, using only the categories you selected (HD, SD, x264, etc ...)")]
public string BaseUrl { get; set; }
public NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));
}
}
}

@ -16,6 +16,7 @@ namespace NzbDrone.Core.Indexers.Newznab
private readonly INewznabCapabilitiesProvider _capabilitiesProvider;
public override string Name => "Newznab";
public override string BaseUrl => Settings.BaseUrl;
public override DownloadProtocol Protocol => DownloadProtocol.Usenet;
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;

@ -5,6 +5,7 @@ using FluentValidation;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.Languages;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Indexers.Newznab
@ -44,7 +45,7 @@ namespace NzbDrone.Core.Indexers.Newznab
}
}
public class NewznabSettings : IIndexerSettings
public class NewznabSettings : IProviderConfig
{
private static readonly NewznabSettingsValidator Validator = new NewznabSettingsValidator();

@ -1,31 +0,0 @@
using NLog;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration;
namespace NzbDrone.Core.Indexers.Nyaa
{
public class Nyaa : HttpIndexerBase<NyaaSettings>
{
public override string Name => "Nyaa";
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
public override int PageSize => 100;
public Nyaa(IHttpClient httpClient, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
: base(httpClient, indexerStatusService, configService, logger)
{
}
public override IIndexerRequestGenerator GetRequestGenerator()
{
return new NyaaRequestGenerator() { Settings = Settings, PageSize = PageSize };
}
public override IParseIndexerResponse GetParser()
{
return new TorrentRssParser() { UseGuidInfoUrl = true, ParseSizeInDescription = true, ParseSeedersInDescription = true };
}
}
}

@ -1,77 +0,0 @@
using System;
using System.Collections.Generic;
using NzbDrone.Common.Http;
using NzbDrone.Core.IndexerSearch.Definitions;
namespace NzbDrone.Core.Indexers.Nyaa
{
public class NyaaRequestGenerator : IIndexerRequestGenerator
{
public NyaaSettings Settings { get; set; }
public int MaxPages { get; set; }
public int PageSize { get; set; }
public NyaaRequestGenerator()
{
MaxPages = 30;
PageSize = 100;
}
private IEnumerable<IndexerRequest> GetPagedRequests(int maxPages, string term)
{
var baseUrl = string.Format("{0}/?page=rss{1}", Settings.BaseUrl.TrimEnd('/'), Settings.AdditionalParameters);
if (term != null)
{
baseUrl += "&term=" + term;
}
if (PageSize == 0)
{
yield return new IndexerRequest(baseUrl, HttpAccept.Rss);
}
else
{
yield return new IndexerRequest(baseUrl, HttpAccept.Rss);
}
}
private string PrepareQuery(string query)
{
return query.Replace(' ', '+');
}
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
{
var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetPagedRequests(MaxPages, PrepareQuery(string.Format("{0}", searchCriteria.SearchTerm))));
return pageableRequests;
}
public IndexerPageableRequestChain GetSearchRequests(MusicSearchCriteria searchCriteria)
{
return new IndexerPageableRequestChain();
}
public IndexerPageableRequestChain GetSearchRequests(TvSearchCriteria searchCriteria)
{
return new IndexerPageableRequestChain();
}
public IndexerPageableRequestChain GetSearchRequests(BookSearchCriteria searchCriteria)
{
return new IndexerPageableRequestChain();
}
public IndexerPageableRequestChain GetSearchRequests(BasicSearchCriteria searchCriteria)
{
return new IndexerPageableRequestChain();
}
public Func<IDictionary<string, string>> GetCookies { get; set; }
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
}
}

@ -1,38 +0,0 @@
using System.Text.RegularExpressions;
using FluentValidation;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Indexers.Nyaa
{
public class NyaaSettingsValidator : AbstractValidator<NyaaSettings>
{
public NyaaSettingsValidator()
{
RuleFor(c => c.BaseUrl).ValidRootUrl();
RuleFor(c => c.AdditionalParameters).Matches("(&[a-z]+=[a-z0-9_]+)*", RegexOptions.IgnoreCase);
}
}
public class NyaaSettings : IIndexerSettings
{
private static readonly NyaaSettingsValidator Validator = new NyaaSettingsValidator();
public NyaaSettings()
{
BaseUrl = "";
AdditionalParameters = "";
}
[FieldDefinition(0, Label = "Website URL")]
public string BaseUrl { get; set; }
[FieldDefinition(1, Label = "Additional Parameters", Advanced = true, HelpText = "Please note if you change the category you will have to add required/restricted rules about the subgroups to avoid foreign language releases.")]
public string AdditionalParameters { get; set; }
public NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));
}
}
}

@ -9,6 +9,7 @@ namespace NzbDrone.Core.Indexers.PassThePopcorn
public class PassThePopcorn : HttpIndexerBase<PassThePopcornSettings>
{
public override string Name => "PassThePopcorn";
public override string BaseUrl => "https://passthepopcorn.me";
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
public override bool SupportsRss => true;
@ -34,6 +35,7 @@ namespace NzbDrone.Core.Indexers.PassThePopcorn
Settings = Settings,
HttpClient = _httpClient,
Logger = _logger,
BaseUrl = BaseUrl
};
}
@ -70,7 +72,7 @@ namespace NzbDrone.Core.Indexers.PassThePopcorn
public override IParseIndexerResponse GetParser()
{
return new PassThePopcornParser(Settings, _logger);
return new PassThePopcornParser(BaseUrl, _logger);
}
}
}

@ -12,11 +12,11 @@ namespace NzbDrone.Core.Indexers.PassThePopcorn
{
public class PassThePopcornParser : IParseIndexerResponse
{
private readonly PassThePopcornSettings _settings;
private readonly string _baseUrl;
private readonly Logger _logger;
public PassThePopcornParser(PassThePopcornSettings settings, Logger logger)
public PassThePopcornParser(string baseUrl, Logger logger)
{
_settings = settings;
_baseUrl = baseUrl;
_logger = logger;
}
@ -129,7 +129,7 @@ namespace NzbDrone.Core.Indexers.PassThePopcorn
private string GetDownloadUrl(int torrentId, string authKey, string passKey)
{
var url = new HttpUri(_settings.BaseUrl)
var url = new HttpUri(_baseUrl)
.CombinePath("/torrents.php")
.AddQueryParam("action", "download")
.AddQueryParam("id", torrentId)
@ -141,7 +141,7 @@ namespace NzbDrone.Core.Indexers.PassThePopcorn
private string GetInfoUrl(string groupId, int torrentId)
{
var url = new HttpUri(_settings.BaseUrl)
var url = new HttpUri(_baseUrl)
.CombinePath("/torrents.php")
.AddQueryParam("id", groupId)
.AddQueryParam("torrentid", torrentId);

@ -9,6 +9,7 @@ namespace NzbDrone.Core.Indexers.PassThePopcorn
{
public class PassThePopcornRequestGenerator : IIndexerRequestGenerator
{
public string BaseUrl { get; set; }
public PassThePopcornSettings Settings { get; set; }
public IDictionary<string, string> Cookies { get; set; }
@ -39,7 +40,7 @@ namespace NzbDrone.Core.Indexers.PassThePopcorn
{
var request =
new IndexerRequest(
$"{Settings.BaseUrl.Trim().TrimEnd('/')}/torrents.php?action=advanced&json=noredirect&searchstr={searchParameters}",
$"{BaseUrl.Trim().TrimEnd('/')}/torrents.php?action=advanced&json=noredirect&searchstr={searchParameters}",
HttpAccept.Json);
request.HttpRequest.Headers["ApiUser"] = Settings.APIUser;

@ -3,6 +3,7 @@ using FluentValidation;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.Languages;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Indexers.PassThePopcorn
@ -11,23 +12,19 @@ namespace NzbDrone.Core.Indexers.PassThePopcorn
{
public PassThePopcornSettingsValidator()
{
RuleFor(c => c.BaseUrl).ValidRootUrl();
RuleFor(c => c.APIUser).NotEmpty();
RuleFor(c => c.APIKey).NotEmpty();
}
}
public class PassThePopcornSettings : IIndexerSettings
public class PassThePopcornSettings : IProviderConfig
{
private static readonly PassThePopcornSettingsValidator Validator = new PassThePopcornSettingsValidator();
public PassThePopcornSettings()
{
BaseUrl = "https://passthepopcorn.me";
}
public string BaseUrl { get; set; }
[FieldDefinition(0, Label = "APIUser", HelpText = "These settings are found in your PassThePopcorn security settings (Edit Profile > Security).", Privacy = PrivacyLevel.UserName)]
public string APIUser { get; set; }

@ -15,6 +15,7 @@ namespace NzbDrone.Core.Indexers.Rarbg
private readonly IRarbgTokenProvider _tokenProvider;
public override string Name => "Rarbg";
public override string BaseUrl => "https://torrentapi.org";
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
@ -50,7 +51,7 @@ namespace NzbDrone.Core.Indexers.Rarbg
public override IIndexerRequestGenerator GetRequestGenerator()
{
return new RarbgRequestGenerator(_tokenProvider) { Settings = Settings };
return new RarbgRequestGenerator(_tokenProvider) { Settings = Settings, BaseUrl = BaseUrl };
}
public override IParseIndexerResponse GetParser()
@ -66,7 +67,7 @@ namespace NzbDrone.Core.Indexers.Rarbg
try
{
var request = new HttpRequestBuilder(Settings.BaseUrl.Trim('/'))
var request = new HttpRequestBuilder(BaseUrl.Trim('/'))
.Resource("/pubapi_v2.php?get_token=get_token")
.Accept(HttpAccept.Json)
.Build();

@ -12,6 +12,7 @@ namespace NzbDrone.Core.Indexers.Rarbg
{
private readonly IRarbgTokenProvider _tokenProvider;
public string BaseUrl { get; set; }
public RarbgSettings Settings { get; set; }
public RarbgRequestGenerator(IRarbgTokenProvider tokenProvider)
@ -28,7 +29,7 @@ namespace NzbDrone.Core.Indexers.Rarbg
private IEnumerable<IndexerRequest> GetMovieRequest(MovieSearchCriteria searchCriteria)
{
var requestBuilder = new HttpRequestBuilder(Settings.BaseUrl)
var requestBuilder = new HttpRequestBuilder(BaseUrl)
.Resource("/pubapi_v2.php")
.Accept(HttpAccept.Json);
@ -62,7 +63,7 @@ namespace NzbDrone.Core.Indexers.Rarbg
requestBuilder.AddQueryParam("category", categoryParam);
requestBuilder.AddQueryParam("limit", "100");
requestBuilder.AddQueryParam("token", _tokenProvider.GetToken(Settings));
requestBuilder.AddQueryParam("token", _tokenProvider.GetToken(Settings, BaseUrl));
requestBuilder.AddQueryParam("format", "json_extended");
requestBuilder.AddQueryParam("app_id", BuildInfo.AppName);

@ -3,6 +3,7 @@ using FluentValidation;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.Languages;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Indexers.Rarbg
@ -11,22 +12,18 @@ namespace NzbDrone.Core.Indexers.Rarbg
{
public RarbgSettingsValidator()
{
RuleFor(c => c.BaseUrl).ValidRootUrl();
}
}
public class RarbgSettings : IIndexerSettings
public class RarbgSettings : IProviderConfig
{
private static readonly RarbgSettingsValidator Validator = new RarbgSettingsValidator();
public RarbgSettings()
{
BaseUrl = "https://torrentapi.org";
RankedOnly = false;
}
public string BaseUrl { get; set; }
[FieldDefinition(1, Type = FieldType.Checkbox, Label = "Ranked Only", HelpText = "Only include ranked results.")]
public bool RankedOnly { get; set; }

@ -9,7 +9,7 @@ namespace NzbDrone.Core.Indexers.Rarbg
{
public interface IRarbgTokenProvider
{
string GetToken(RarbgSettings settings);
string GetToken(RarbgSettings settings, string baseUrl);
}
public class RarbgTokenProvider : IRarbgTokenProvider
@ -25,12 +25,12 @@ namespace NzbDrone.Core.Indexers.Rarbg
_logger = logger;
}
public string GetToken(RarbgSettings settings)
public string GetToken(RarbgSettings settings, string baseUrl)
{
return _tokenCache.Get(settings.BaseUrl,
return _tokenCache.Get(baseUrl,
() =>
{
var requestBuilder = new HttpRequestBuilder(settings.BaseUrl.Trim('/'))
var requestBuilder = new HttpRequestBuilder(baseUrl.Trim('/'))
.WithRateLimit(3.0)
.Resource("/pubapi_v2.php?get_token=get_token&app_id=Prowlarr")
.Accept(HttpAccept.Json);

@ -0,0 +1,52 @@
using System.Collections.Generic;
using NLog;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration;
namespace NzbDrone.Core.Indexers.Definitions
{
public class Redacted : Gazelle.Gazelle
{
public override string Name => "Redacted";
public override string BaseUrl => "https://redacted.ch/";
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
public Redacted(IHttpClient httpClient, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
: base(httpClient, indexerStatusService, configService, logger)
{
}
protected override IndexerCapabilities SetCapabilities()
{
var caps = new IndexerCapabilities
{
TvSearchParams = new List<TvSearchParam>
{
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep
},
MovieSearchParams = new List<MovieSearchParam>
{
MovieSearchParam.Q
},
MusicSearchParams = new List<MusicSearchParam>
{
MusicSearchParam.Q, MusicSearchParam.Album, MusicSearchParam.Artist, MusicSearchParam.Label, MusicSearchParam.Year
},
BookSearchParams = new List<BookSearchParam>
{
BookSearchParam.Q
}
};
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.Audio, "Music");
caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.PC, "Applications");
caps.Categories.AddCategoryMapping(3, NewznabStandardCategory.Books, "E-Books");
caps.Categories.AddCategoryMapping(4, NewznabStandardCategory.AudioAudiobook, "Audiobooks");
caps.Categories.AddCategoryMapping(5, NewznabStandardCategory.Movies, "E-Learning Videos");
caps.Categories.AddCategoryMapping(6, NewznabStandardCategory.TV, "Comedy");
caps.Categories.AddCategoryMapping(7, NewznabStandardCategory.Books, "Comics");
return caps;
}
}
}

@ -8,6 +8,7 @@ namespace NzbDrone.Core.Indexers.TorrentPotato
public class TorrentPotato : HttpIndexerBase<TorrentPotatoSettings>
{
public override string Name => "TorrentPotato";
public override string BaseUrl => "http://127.0.0.1";
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
@ -36,7 +37,7 @@ namespace NzbDrone.Core.Indexers.TorrentPotato
public override IIndexerRequestGenerator GetRequestGenerator()
{
return new TorrentPotatoRequestGenerator() { Settings = Settings };
return new TorrentPotatoRequestGenerator() { Settings = Settings, BaseUrl = BaseUrl };
}
public override IParseIndexerResponse GetParser()

@ -9,6 +9,7 @@ namespace NzbDrone.Core.Indexers.TorrentPotato
{
public class TorrentPotatoRequestGenerator : IIndexerRequestGenerator
{
public string BaseUrl { get; set; }
public TorrentPotatoSettings Settings { get; set; }
public TorrentPotatoRequestGenerator()
@ -26,7 +27,7 @@ namespace NzbDrone.Core.Indexers.TorrentPotato
private IEnumerable<IndexerRequest> GetPagedRequests(string mode, int? tvdbId, string query, params object[] args)
{
var requestBuilder = new HttpRequestBuilder(Settings.BaseUrl)
var requestBuilder = new HttpRequestBuilder(BaseUrl)
.Accept(HttpAccept.Json);
requestBuilder.AddQueryParam("passkey", Settings.Passkey);
@ -46,7 +47,7 @@ namespace NzbDrone.Core.Indexers.TorrentPotato
private IEnumerable<IndexerRequest> GetMovieRequest(MovieSearchCriteria searchCriteria)
{
var requestBuilder = new HttpRequestBuilder(Settings.BaseUrl)
var requestBuilder = new HttpRequestBuilder(BaseUrl)
.Accept(HttpAccept.Json);
requestBuilder.AddQueryParam("passkey", Settings.Passkey);

@ -1,8 +1,6 @@
using System.Collections.Generic;
using FluentValidation;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.Languages;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Indexers.TorrentPotato
@ -11,22 +9,17 @@ namespace NzbDrone.Core.Indexers.TorrentPotato
{
public TorrentPotatoSettingsValidator()
{
RuleFor(c => c.BaseUrl).ValidRootUrl();
}
}
public class TorrentPotatoSettings : IIndexerSettings
public class TorrentPotatoSettings : IProviderConfig
{
private static readonly TorrentPotatoSettingsValidator Validator = new TorrentPotatoSettingsValidator();
public TorrentPotatoSettings()
{
BaseUrl = "http://127.0.0.1";
}
[FieldDefinition(0, Label = "API URL", HelpText = "URL to TorrentPotato api.")]
public string BaseUrl { get; set; }
[FieldDefinition(1, Label = "Username", HelpText = "The username you use at your indexer.", Privacy = PrivacyLevel.UserName)]
public string User { get; set; }

@ -1,34 +0,0 @@
using NLog;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration;
namespace NzbDrone.Core.Indexers.TorrentRss
{
public class TorrentRssIndexer : HttpIndexerBase<TorrentRssIndexerSettings>
{
public override string Name => "Torrent RSS Feed";
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
public override bool SupportsSearch => false;
public override int PageSize => 0;
private readonly ITorrentRssParserFactory _torrentRssParserFactory;
public TorrentRssIndexer(ITorrentRssParserFactory torrentRssParserFactory, IHttpClient httpClient, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
: base(httpClient, indexerStatusService, configService, logger)
{
_torrentRssParserFactory = torrentRssParserFactory;
}
public override IIndexerRequestGenerator GetRequestGenerator()
{
return new TorrentRssIndexerRequestGenerator { Settings = Settings };
}
public override IParseIndexerResponse GetParser()
{
return _torrentRssParserFactory.GetParser(Settings);
}
}
}

@ -1,12 +0,0 @@
namespace NzbDrone.Core.Indexers.TorrentRss
{
public class TorrentRssIndexerParserSettings
{
public bool UseEZTVFormat { get; set; }
public bool ParseSeedersInDescription { get; set; }
public bool UseEnclosureUrl { get; set; }
public bool UseEnclosureLength { get; set; }
public bool ParseSizeInDescription { get; set; }
public string SizeElementName { get; set; }
}
}

@ -1,63 +0,0 @@
using System;
using System.Collections.Generic;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.IndexerSearch.Definitions;
namespace NzbDrone.Core.Indexers.TorrentRss
{
public class TorrentRssIndexerRequestGenerator : IIndexerRequestGenerator
{
public TorrentRssIndexerSettings Settings { get; set; }
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
{
var pageableRequests = new IndexerPageableRequestChain();
if (searchCriteria.SearchTerm.IsNullOrWhiteSpace())
{
pageableRequests.Add(GetRssRequests(null));
}
return pageableRequests;
}
private IEnumerable<IndexerRequest> GetRssRequests(string searchParameters)
{
var request = new IndexerRequest(Settings.BaseUrl.Trim().TrimEnd('/'), HttpAccept.Rss);
if (Settings.Cookie.IsNotNullOrWhiteSpace())
{
foreach (var cookie in HttpHeader.ParseCookies(Settings.Cookie))
{
request.HttpRequest.Cookies[cookie.Key] = cookie.Value;
}
}
yield return request;
}
public IndexerPageableRequestChain GetSearchRequests(MusicSearchCriteria searchCriteria)
{
return new IndexerPageableRequestChain();
}
public IndexerPageableRequestChain GetSearchRequests(TvSearchCriteria searchCriteria)
{
return new IndexerPageableRequestChain();
}
public IndexerPageableRequestChain GetSearchRequests(BookSearchCriteria searchCriteria)
{
return new IndexerPageableRequestChain();
}
public IndexerPageableRequestChain GetSearchRequests(BasicSearchCriteria searchCriteria)
{
return new IndexerPageableRequestChain();
}
public Func<IDictionary<string, string>> GetCookies { get; set; }
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
}
}

@ -1,42 +0,0 @@
using System.Collections.Generic;
using FluentValidation;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.Languages;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Indexers.TorrentRss
{
public class TorrentRssIndexerSettingsValidator : AbstractValidator<TorrentRssIndexerSettings>
{
public TorrentRssIndexerSettingsValidator()
{
RuleFor(c => c.BaseUrl).ValidRootUrl();
}
}
public class TorrentRssIndexerSettings : IIndexerSettings
{
private static readonly TorrentRssIndexerSettingsValidator Validator = new TorrentRssIndexerSettingsValidator();
public TorrentRssIndexerSettings()
{
BaseUrl = string.Empty;
AllowZeroSize = false;
}
[FieldDefinition(0, Label = "Full RSS Feed URL")]
public string BaseUrl { get; set; }
[FieldDefinition(1, Label = "Cookie", HelpText = "If you site requires a login cookie to access the rss, you'll have to retrieve it via a browser.")]
public string Cookie { get; set; }
[FieldDefinition(2, Type = FieldType.Checkbox, Label = "Allow Zero Size", HelpText = "Enabling this will allow you to use feeds that don't specify release size, but be careful, size related checks will not be performed.")]
public bool AllowZeroSize { get; set; }
public NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));
}
}
}

@ -1,65 +0,0 @@
using System;
using NLog;
using NzbDrone.Common.Cache;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.Indexers.Exceptions;
namespace NzbDrone.Core.Indexers.TorrentRss
{
public interface ITorrentRssParserFactory
{
TorrentRssParser GetParser(TorrentRssIndexerSettings settings);
}
public class TorrentRssParserFactory : ITorrentRssParserFactory
{
protected readonly Logger _logger;
private readonly ICached<TorrentRssIndexerParserSettings> _settingsCache;
private readonly ITorrentRssSettingsDetector _torrentRssSettingsDetector;
public TorrentRssParserFactory(ICacheManager cacheManager, ITorrentRssSettingsDetector torrentRssSettingsDetector, Logger logger)
{
_settingsCache = cacheManager.GetCache<TorrentRssIndexerParserSettings>(GetType());
_torrentRssSettingsDetector = torrentRssSettingsDetector;
_logger = logger;
}
public TorrentRssParser GetParser(TorrentRssIndexerSettings indexerSettings)
{
var key = indexerSettings.ToJson();
var parserSettings = _settingsCache.Get(key, () => DetectParserSettings(indexerSettings), TimeSpan.FromDays(7));
if (parserSettings.UseEZTVFormat)
{
return new EzrssTorrentRssParser();
}
else
{
return new TorrentRssParser
{
UseGuidInfoUrl = false,
ParseSeedersInDescription = parserSettings.ParseSeedersInDescription,
UseEnclosureUrl = parserSettings.UseEnclosureUrl,
UseEnclosureLength = parserSettings.UseEnclosureLength,
ParseSizeInDescription = parserSettings.ParseSizeInDescription,
SizeElementName = parserSettings.SizeElementName
};
}
}
private TorrentRssIndexerParserSettings DetectParserSettings(TorrentRssIndexerSettings indexerSettings)
{
var settings = _torrentRssSettingsDetector.Detect(indexerSettings);
if (settings == null)
{
throw new UnsupportedFeedException("Could not parse feed from {0}", indexerSettings.BaseUrl);
}
return settings;
}
}
}

@ -1,307 +0,0 @@
using System;
using System.IO;
using System.Linq;
using System.Xml;
using System.Xml.Linq;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Indexers.Exceptions;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.Indexers.TorrentRss
{
public interface ITorrentRssSettingsDetector
{
TorrentRssIndexerParserSettings Detect(TorrentRssIndexerSettings settings);
}
public class TorrentRssSettingsDetector : ITorrentRssSettingsDetector
{
private const long ValidSizeThreshold = 2 * 1024 * 1024;
protected readonly Logger _logger;
private readonly IHttpClient _httpClient;
public TorrentRssSettingsDetector(IHttpClient httpClient, Logger logger)
{
_httpClient = httpClient;
_logger = logger;
}
/// <summary>
/// Detect settings for Parser, based on URL
/// </summary>
/// <param name="settings">Indexer Settings to use for Parser</param>
/// <returns>Parsed Settings or <c>null</c></returns>
public TorrentRssIndexerParserSettings Detect(TorrentRssIndexerSettings indexerSettings)
{
_logger.Debug("Evaluating TorrentRss feed '{0}'", indexerSettings.BaseUrl);
var requestGenerator = new TorrentRssIndexerRequestGenerator { Settings = indexerSettings };
var request = requestGenerator.GetSearchRequests(new IndexerSearch.Definitions.MovieSearchCriteria()).GetAllTiers().First().First();
HttpResponse httpResponse = null;
try
{
httpResponse = _httpClient.Execute(request.HttpRequest);
}
catch (Exception ex)
{
_logger.Warn(ex, string.Format("Unable to connect to indexer {0}: {1}", request.Url, ex.Message));
return null;
}
var indexerResponse = new IndexerResponse(request, httpResponse);
return GetParserSettings(indexerResponse, indexerSettings);
}
private TorrentRssIndexerParserSettings GetParserSettings(IndexerResponse response, TorrentRssIndexerSettings indexerSettings)
{
var settings = GetEzrssParserSettings(response, indexerSettings);
if (settings != null)
{
return settings;
}
settings = GetGenericTorrentRssParserSettings(response, indexerSettings);
if (settings != null)
{
return settings;
}
return null;
}
private TorrentRssIndexerParserSettings GetEzrssParserSettings(IndexerResponse response, TorrentRssIndexerSettings indexerSettings)
{
if (!IsEZTVFeed(response))
{
return null;
}
_logger.Trace("Feed has Ezrss schema");
var parser = new EzrssTorrentRssParser();
var releases = ParseResponse(parser, response);
try
{
ValidateReleases(releases, indexerSettings);
ValidateReleaseSize(releases, indexerSettings);
_logger.Debug("Feed was parseable by Ezrss Parser");
return new TorrentRssIndexerParserSettings
{
UseEZTVFormat = true
};
}
catch (Exception ex)
{
_logger.Trace(ex, "Feed wasn't parsable by Ezrss Parser");
return null;
}
}
private TorrentRssIndexerParserSettings GetGenericTorrentRssParserSettings(IndexerResponse response, TorrentRssIndexerSettings indexerSettings)
{
var parser = new TorrentRssParser
{
UseEnclosureUrl = true,
UseEnclosureLength = true,
ParseSeedersInDescription = true
};
var item = parser.GetItems(response).FirstOrDefault();
if (item == null)
{
throw new UnsupportedFeedException("Empty feed, cannot check if feed is parsable.");
}
var settings = new TorrentRssIndexerParserSettings()
{
UseEnclosureUrl = true,
UseEnclosureLength = true,
ParseSeedersInDescription = true
};
if (item.Element("enclosure") == null)
{
parser.UseEnclosureUrl = settings.UseEnclosureUrl = false;
}
var releases = ParseResponse(parser, response);
ValidateReleases(releases, indexerSettings);
if (!releases.Any(v => v.Seeders.HasValue))
{
_logger.Trace("Feed doesn't have Seeders in Description, disabling option.");
parser.ParseSeedersInDescription = settings.ParseSeedersInDescription = false;
}
if (!releases.Any(r => r.Size < ValidSizeThreshold))
{
_logger.Trace("Feed has valid size in enclosure.");
return settings;
}
parser.UseEnclosureLength = settings.UseEnclosureLength = false;
foreach (var sizeElementName in new[] { "size", "Size" })
{
parser.SizeElementName = settings.SizeElementName = sizeElementName;
releases = ParseResponse(parser, response);
ValidateReleases(releases, indexerSettings);
if (!releases.Any(r => r.Size < ValidSizeThreshold))
{
_logger.Trace("Feed has valid size in Size element.");
return settings;
}
}
parser.SizeElementName = settings.SizeElementName = null;
parser.ParseSizeInDescription = settings.ParseSizeInDescription = true;
releases = ParseResponse(parser, response);
ValidateReleases(releases, indexerSettings);
if (releases.Count(r => r.Size >= ValidSizeThreshold) > releases.Length / 2)
{
if (releases.Any(r => r.Size < ValidSizeThreshold))
{
_logger.Debug("Feed {0} contains very small releases.", response.Request.Url);
}
_logger.Trace("Feed has valid size in description.");
return settings;
}
parser.ParseSizeInDescription = settings.ParseSizeInDescription = false;
_logger.Debug("Feed doesn't have release size.");
releases = ParseResponse(parser, response);
ValidateReleases(releases, indexerSettings);
ValidateReleaseSize(releases, indexerSettings);
return settings;
}
private bool IsEZTVFeed(IndexerResponse response)
{
var content = XmlCleaner.ReplaceEntities(response.Content);
content = XmlCleaner.ReplaceUnicode(content);
using (var xmlTextReader = XmlReader.Create(new StringReader(content), new XmlReaderSettings { DtdProcessing = DtdProcessing.Parse, ValidationType = ValidationType.None, IgnoreComments = true, XmlResolver = null }))
{
var document = XDocument.Load(xmlTextReader);
// Check Namespace
if (document.Root == null)
{
throw new InvalidDataException("Could not parse IndexerResponse into XML.");
}
var ns = document.Root.GetNamespaceOfPrefix("torrent");
if (ns == "http://xmlns.ezrss.it/0.1/")
{
_logger.Trace("Identified feed as EZTV compatible by EZTV Namespace");
return true;
}
// Check DTD in DocType
if (document.DocumentType != null && document.DocumentType.SystemId == "http://xmlns.ezrss.it/0.1/dtd/")
{
_logger.Trace("Identified feed as EZTV compatible by EZTV DTD");
return true;
}
// Check namespaces
if (document.Descendants().Any(v => v.GetDefaultNamespace().NamespaceName == "http://xmlns.ezrss.it/0.1/"))
{
_logger.Trace("Identified feed as EZTV compatible by EZTV Namespace");
return true;
}
return false;
}
}
private TorrentInfo[] ParseResponse(IParseIndexerResponse parser, IndexerResponse response)
{
try
{
var releases = parser.ParseResponse(response).Cast<TorrentInfo>().ToArray();
return releases;
}
catch (Exception ex)
{
_logger.Debug(ex, "Unable to parse indexer feed: " + ex.Message);
throw new UnsupportedFeedException("Unable to parse indexer: " + ex.Message);
}
}
private void ValidateReleases(TorrentInfo[] releases, TorrentRssIndexerSettings indexerSettings)
{
if (releases == null || releases.Empty())
{
throw new UnsupportedFeedException("Empty feed, cannot check if feed is parsable.");
}
var torrentInfo = releases.First();
_logger.Trace("TorrentInfo: \n{0}", torrentInfo.ToString("L"));
if (releases.Any(r => r.Title.IsNullOrWhiteSpace()))
{
throw new UnsupportedFeedException("Feed contains releases without title.");
}
if (releases.Any(r => !IsValidDownloadUrl(r.DownloadUrl)))
{
throw new UnsupportedFeedException("Failed to find a valid download url in the feed.");
}
var total = releases.Where(v => v.Guid != null).Select(v => v.Guid).ToArray();
var distinct = total.Distinct().ToArray();
if (distinct.Length != total.Length)
{
throw new UnsupportedFeedException("Feed contains releases with same guid, rejecting malformed rss feed.");
}
}
private void ValidateReleaseSize(TorrentInfo[] releases, TorrentRssIndexerSettings indexerSettings)
{
if (!indexerSettings.AllowZeroSize && releases.Any(r => r.Size == 0))
{
throw new UnsupportedFeedException("Feed doesn't contain the release content size.");
}
if (releases.Any(r => r.Size != 0 && r.Size < ValidSizeThreshold))
{
throw new UnsupportedFeedException("Size of one more releases lower than {0}, feed must contain release content size.", ValidSizeThreshold.SizeSuffix());
}
}
private static bool IsValidDownloadUrl(string url)
{
if (url.IsNullOrWhiteSpace())
{
return false;
}
if (url.StartsWith("magnet:") ||
url.StartsWith("http:") ||
url.StartsWith("https:"))
{
return true;
}
return false;
}
}
}

@ -17,6 +17,7 @@ namespace NzbDrone.Core.Indexers.Torznab
private readonly INewznabCapabilitiesProvider _capabilitiesProvider;
public override string Name => "Torznab";
public override string BaseUrl => Settings.BaseUrl;
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;

@ -12,11 +12,12 @@ using NzbDrone.Core.Http.CloudFlare;
using NzbDrone.Core.Indexers.Exceptions;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.ThingiProvider;
namespace NzbDrone.Core.Indexers
{
public abstract class HttpIndexerBase<TSettings> : IndexerBase<TSettings>
where TSettings : IIndexerSettings, new()
where TSettings : IProviderConfig, new()
{
protected const int MaxNumResultsPerQuery = 1000;
@ -25,9 +26,6 @@ namespace NzbDrone.Core.Indexers
public override bool SupportsRss => true;
public override bool SupportsSearch => true;
public override IndexerCapabilities Capabilities { get; protected set; }
public bool SupportsPaging => PageSize > 0;
public virtual int PageSize => 0;
public virtual TimeSpan RateLimit => TimeSpan.FromSeconds(2);

@ -1,10 +0,0 @@
using System.Collections.Generic;
using NzbDrone.Core.ThingiProvider;
namespace NzbDrone.Core.Indexers
{
public interface IIndexerSettings : IProviderConfig
{
string BaseUrl { get; set; }
}
}

@ -12,13 +12,14 @@ using NzbDrone.Core.ThingiProvider;
namespace NzbDrone.Core.Indexers
{
public abstract class IndexerBase<TSettings> : IIndexer
where TSettings : IIndexerSettings, new()
where TSettings : IProviderConfig, new()
{
protected readonly IIndexerStatusService _indexerStatusService;
protected readonly IConfigService _configService;
protected readonly Logger _logger;
public abstract string Name { get; }
public abstract string BaseUrl { get; }
public abstract DownloadProtocol Protocol { get; }
public abstract IndexerPrivacy Privacy { get; }
public int Priority { get; set; }

@ -105,18 +105,19 @@ namespace NzbDrone.Core.Indexers
return cats;
}
public ICollection<int> MapTrackerCatDescToNewznab(string trackerCategoryDesc)
public ICollection<IndexerCategory> MapTrackerCatDescToNewznab(string trackerCategoryDesc)
{
if (string.IsNullOrWhiteSpace(trackerCategoryDesc))
{
return new List<int>();
return new List<IndexerCategory>();
}
var cats = _categoryMapping
.Where(m =>
!string.IsNullOrWhiteSpace(m.TrackerCategoryDesc) &&
string.Equals(m.TrackerCategoryDesc, trackerCategoryDesc, StringComparison.InvariantCultureIgnoreCase))
.Select(c => c.NewzNabCategory).ToList();
.Select(c => NewznabStandardCategory.AllCats.FirstOrDefault(n => n.Id == c.NewzNabCategory) ?? new IndexerCategory { Id = c.NewzNabCategory })
.ToList();
return cats;
}

Loading…
Cancel
Save