parent
0fb7168669
commit
30367b53fd
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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,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;
|
||||
}
|
||||
}
|
@ -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,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,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));
|
||||
}
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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,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,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;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
|
||||
namespace NzbDrone.Core.Indexers
|
||||
{
|
||||
public interface IIndexerSettings : IProviderConfig
|
||||
{
|
||||
string BaseUrl { get; set; }
|
||||
}
|
||||
}
|
Loading…
Reference in new issue