parent
7287abc77c
commit
ede9879c99
@ -1,280 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Collections.Specialized;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Net.Http;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using AngleSharp.Html.Parser;
|
|
||||||
using NLog;
|
|
||||||
using NzbDrone.Common.Extensions;
|
|
||||||
using NzbDrone.Common.Http;
|
|
||||||
using NzbDrone.Core.Configuration;
|
|
||||||
using NzbDrone.Core.Indexers.Exceptions;
|
|
||||||
using NzbDrone.Core.Indexers.Settings;
|
|
||||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
|
||||||
using NzbDrone.Core.Messaging.Events;
|
|
||||||
using NzbDrone.Core.Parser;
|
|
||||||
using NzbDrone.Core.Parser.Model;
|
|
||||||
|
|
||||||
namespace NzbDrone.Core.Indexers.Definitions
|
|
||||||
{
|
|
||||||
[Obsolete("Moved to YML for Cardigann")]
|
|
||||||
public class Anthelion : TorrentIndexerBase<UserPassTorrentBaseSettings>
|
|
||||||
{
|
|
||||||
public override string Name => "Anthelion";
|
|
||||||
public override string[] IndexerUrls => new string[] { "https://anthelion.me/" };
|
|
||||||
private string LoginUrl => Settings.BaseUrl + "login.php";
|
|
||||||
public override string Description => "A movies tracker";
|
|
||||||
public override string Language => "en-US";
|
|
||||||
public override Encoding Encoding => Encoding.UTF8;
|
|
||||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
|
|
||||||
public override IndexerCapabilities Capabilities => SetCapabilities();
|
|
||||||
|
|
||||||
public Anthelion(IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
|
|
||||||
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public override IIndexerRequestGenerator GetRequestGenerator()
|
|
||||||
{
|
|
||||||
return new AnthelionRequestGenerator() { Settings = Settings, Capabilities = Capabilities };
|
|
||||||
}
|
|
||||||
|
|
||||||
public override IParseIndexerResponse GetParser()
|
|
||||||
{
|
|
||||||
return new AnthelionParser(Settings, Capabilities.Categories);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override async Task DoLogin()
|
|
||||||
{
|
|
||||||
var requestBuilder = new HttpRequestBuilder(LoginUrl)
|
|
||||||
{
|
|
||||||
LogResponseContent = true,
|
|
||||||
AllowAutoRedirect = true,
|
|
||||||
Method = HttpMethod.Post
|
|
||||||
};
|
|
||||||
|
|
||||||
var cookies = Cookies;
|
|
||||||
Cookies = null;
|
|
||||||
|
|
||||||
var authLoginRequest = requestBuilder
|
|
||||||
.AddFormParameter("username", Settings.Username)
|
|
||||||
.AddFormParameter("password", Settings.Password)
|
|
||||||
.AddFormParameter("keeplogged", "1")
|
|
||||||
.AddFormParameter("login", "Log+In!")
|
|
||||||
.SetHeader("Content-Type", "application/x-www-form-urlencoded")
|
|
||||||
.SetHeader("Referer", LoginUrl)
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
var response = await ExecuteAuth(authLoginRequest);
|
|
||||||
|
|
||||||
if (CheckIfLoginNeeded(response))
|
|
||||||
{
|
|
||||||
var parser = new HtmlParser();
|
|
||||||
using var dom = parser.ParseDocument(response.Content);
|
|
||||||
var errorMessage = dom.QuerySelector("form#loginform")?.TextContent.Trim();
|
|
||||||
|
|
||||||
throw new IndexerAuthException(errorMessage ?? "Unknown error message, please report.");
|
|
||||||
}
|
|
||||||
|
|
||||||
cookies = response.GetCookies();
|
|
||||||
UpdateCookies(cookies, DateTime.Now.AddDays(30));
|
|
||||||
|
|
||||||
_logger.Debug("Anthelion authentication succeeded.");
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override bool CheckIfLoginNeeded(HttpResponse httpResponse)
|
|
||||||
{
|
|
||||||
return !httpResponse.Content.Contains("logout.php");
|
|
||||||
}
|
|
||||||
|
|
||||||
private IndexerCapabilities SetCapabilities()
|
|
||||||
{
|
|
||||||
var caps = new IndexerCapabilities
|
|
||||||
{
|
|
||||||
TvSearchParams = new List<TvSearchParam>
|
|
||||||
{
|
|
||||||
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep
|
|
||||||
},
|
|
||||||
MovieSearchParams = new List<MovieSearchParam>
|
|
||||||
{
|
|
||||||
MovieSearchParam.Q
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
caps.Categories.AddCategoryMapping("1", NewznabStandardCategory.Movies, "Film/Feature");
|
|
||||||
caps.Categories.AddCategoryMapping("2", NewznabStandardCategory.Movies, "Film/Short");
|
|
||||||
caps.Categories.AddCategoryMapping("3", NewznabStandardCategory.TV, "TV/Miniseries");
|
|
||||||
caps.Categories.AddCategoryMapping("4", NewznabStandardCategory.Other, "Other");
|
|
||||||
|
|
||||||
return caps;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class AnthelionRequestGenerator : IIndexerRequestGenerator
|
|
||||||
{
|
|
||||||
public UserPassTorrentBaseSettings Settings { get; set; }
|
|
||||||
public IndexerCapabilities Capabilities { get; set; }
|
|
||||||
|
|
||||||
private IEnumerable<IndexerRequest> GetPagedRequests(string term, int[] categories, string imdbId = null)
|
|
||||||
{
|
|
||||||
var searchUrl = string.Format("{0}/torrents.php", Settings.BaseUrl.TrimEnd('/'));
|
|
||||||
|
|
||||||
// TODO: IMDB search is available but it requires to parse the details page
|
|
||||||
var qc = new NameValueCollection
|
|
||||||
{
|
|
||||||
{ "order_by", "time" },
|
|
||||||
{ "order_way", "desc" },
|
|
||||||
{ "action", "basic" },
|
|
||||||
{ "searchsubmit", "1" },
|
|
||||||
{ "searchstr", imdbId.IsNotNullOrWhiteSpace() ? imdbId : term.Replace(".", " ") }
|
|
||||||
};
|
|
||||||
|
|
||||||
var catList = Capabilities.Categories.MapTorznabCapsToTrackers(categories);
|
|
||||||
|
|
||||||
foreach (var cat in catList)
|
|
||||||
{
|
|
||||||
qc.Add($"filter_cat[{cat}]", "1");
|
|
||||||
}
|
|
||||||
|
|
||||||
searchUrl = searchUrl + "?" + qc.GetQueryString();
|
|
||||||
|
|
||||||
var request = new IndexerRequest(searchUrl, HttpAccept.Html);
|
|
||||||
|
|
||||||
yield return request;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
|
|
||||||
{
|
|
||||||
var pageableRequests = new IndexerPageableRequestChain();
|
|
||||||
|
|
||||||
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories, searchCriteria.FullImdbId));
|
|
||||||
|
|
||||||
return pageableRequests;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IndexerPageableRequestChain GetSearchRequests(MusicSearchCriteria searchCriteria)
|
|
||||||
{
|
|
||||||
var pageableRequests = new IndexerPageableRequestChain();
|
|
||||||
|
|
||||||
return pageableRequests;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IndexerPageableRequestChain GetSearchRequests(TvSearchCriteria searchCriteria)
|
|
||||||
{
|
|
||||||
var pageableRequests = new IndexerPageableRequestChain();
|
|
||||||
|
|
||||||
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedTvSearchString), searchCriteria.Categories, searchCriteria.FullImdbId));
|
|
||||||
|
|
||||||
return pageableRequests;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IndexerPageableRequestChain GetSearchRequests(BookSearchCriteria searchCriteria)
|
|
||||||
{
|
|
||||||
var pageableRequests = new IndexerPageableRequestChain();
|
|
||||||
|
|
||||||
return pageableRequests;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IndexerPageableRequestChain GetSearchRequests(BasicSearchCriteria searchCriteria)
|
|
||||||
{
|
|
||||||
var pageableRequests = new IndexerPageableRequestChain();
|
|
||||||
|
|
||||||
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories));
|
|
||||||
|
|
||||||
return pageableRequests;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Func<IDictionary<string, string>> GetCookies { get; set; }
|
|
||||||
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class AnthelionParser : IParseIndexerResponse
|
|
||||||
{
|
|
||||||
private readonly UserPassTorrentBaseSettings _settings;
|
|
||||||
private readonly IndexerCapabilitiesCategories _categories;
|
|
||||||
|
|
||||||
public AnthelionParser(UserPassTorrentBaseSettings settings, IndexerCapabilitiesCategories categories)
|
|
||||||
{
|
|
||||||
_settings = settings;
|
|
||||||
_categories = categories;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
|
|
||||||
{
|
|
||||||
var torrentInfos = new List<ReleaseInfo>();
|
|
||||||
|
|
||||||
var parser = new HtmlParser();
|
|
||||||
using var doc = parser.ParseDocument(indexerResponse.Content);
|
|
||||||
var rows = doc.QuerySelectorAll("table.torrent_table > tbody > tr.torrent");
|
|
||||||
foreach (var row in rows)
|
|
||||||
{
|
|
||||||
var qDetailsLink = row.QuerySelector("a.torrent_name");
|
|
||||||
var year = qDetailsLink.NextSibling.TextContent.Replace("[", "").Replace("]", "").Trim();
|
|
||||||
var tags = row.QuerySelector("div.torrent_info").FirstChild.TextContent.Replace(" / ", " ").Trim();
|
|
||||||
var title = $"{qDetailsLink.TextContent} {year} {tags}";
|
|
||||||
var description = row.QuerySelector("div.tags").TextContent.Trim();
|
|
||||||
var details = _settings.BaseUrl + qDetailsLink.GetAttribute("href");
|
|
||||||
var torrentId = qDetailsLink.GetAttribute("href").Split('=').Last();
|
|
||||||
var link = _settings.BaseUrl + "torrents.php?action=download&id=" + torrentId;
|
|
||||||
var posterStr = qDetailsLink.GetAttribute("data-cover");
|
|
||||||
var poster = !string.IsNullOrWhiteSpace(posterStr) ? posterStr : null;
|
|
||||||
|
|
||||||
var files = ParseUtil.CoerceInt(row.QuerySelector("td:nth-child(3)").TextContent);
|
|
||||||
var publishDate = DateTimeUtil.FromTimeAgo(row.QuerySelector("td:nth-child(4)").TextContent);
|
|
||||||
var size = ParseUtil.GetBytes(row.QuerySelector("td:nth-child(5)").FirstChild.TextContent);
|
|
||||||
var grabs = ParseUtil.CoerceInt(row.QuerySelector("td:nth-child(6)").TextContent);
|
|
||||||
var seeders = ParseUtil.CoerceInt(row.QuerySelector("td:nth-child(7)").TextContent);
|
|
||||||
var leechers = ParseUtil.CoerceInt(row.QuerySelector("td:nth-child(8)").TextContent);
|
|
||||||
|
|
||||||
var dlVolumeFactor = row.QuerySelector("strong.tl_free") != null ? 0 : 1;
|
|
||||||
|
|
||||||
var cat = row.QuerySelector("td.cats_col > div").GetAttribute("class").Replace("tooltip cats_", "");
|
|
||||||
var category = new List<IndexerCategory>
|
|
||||||
{
|
|
||||||
cat switch
|
|
||||||
{
|
|
||||||
"featurefilm" => NewznabStandardCategory.Movies,
|
|
||||||
"shortfilm" => NewznabStandardCategory.Movies,
|
|
||||||
"miniseries" => NewznabStandardCategory.TV,
|
|
||||||
"other" => NewznabStandardCategory.Other,
|
|
||||||
_ => throw new Exception($"Unknown category: {cat}")
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO: TMDb is also available
|
|
||||||
var qImdb = row.QuerySelector("a[href^=\"https://www.imdb.com\"]");
|
|
||||||
var imdb = qImdb != null ? ParseUtil.GetImdbId(qImdb.GetAttribute("href").Split('/').Last()) : null;
|
|
||||||
|
|
||||||
var release = new TorrentInfo
|
|
||||||
{
|
|
||||||
MinimumRatio = 1,
|
|
||||||
MinimumSeedTime = 259200,
|
|
||||||
Description = description,
|
|
||||||
Title = title,
|
|
||||||
PublishDate = publishDate,
|
|
||||||
Categories = category,
|
|
||||||
DownloadUrl = link,
|
|
||||||
InfoUrl = details,
|
|
||||||
PosterUrl = poster,
|
|
||||||
Guid = link,
|
|
||||||
ImdbId = imdb.GetValueOrDefault(),
|
|
||||||
Seeders = seeders,
|
|
||||||
Peers = leechers + seeders,
|
|
||||||
Size = size,
|
|
||||||
Grabs = grabs,
|
|
||||||
Files = files,
|
|
||||||
DownloadVolumeFactor = dlVolumeFactor,
|
|
||||||
UploadVolumeFactor = 1
|
|
||||||
};
|
|
||||||
|
|
||||||
torrentInfos.Add(release);
|
|
||||||
}
|
|
||||||
|
|
||||||
return torrentInfos.ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,299 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Collections.Specialized;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using AngleSharp.Html.Dom;
|
|
||||||
using AngleSharp.Html.Parser;
|
|
||||||
using NLog;
|
|
||||||
using NzbDrone.Common.Extensions;
|
|
||||||
using NzbDrone.Common.Http;
|
|
||||||
using NzbDrone.Core.Configuration;
|
|
||||||
using NzbDrone.Core.Indexers.Settings;
|
|
||||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
|
||||||
using NzbDrone.Core.Messaging.Events;
|
|
||||||
using NzbDrone.Core.Parser;
|
|
||||||
using NzbDrone.Core.Parser.Model;
|
|
||||||
|
|
||||||
namespace NzbDrone.Core.Indexers.Definitions;
|
|
||||||
|
|
||||||
[Obsolete("User Agent blocked")]
|
|
||||||
public class AudioBookBay : TorrentIndexerBase<NoAuthTorrentBaseSettings>
|
|
||||||
{
|
|
||||||
public override string Name => "AudioBook Bay";
|
|
||||||
public override string[] IndexerUrls => new[]
|
|
||||||
{
|
|
||||||
"https://audiobookbay.is/"
|
|
||||||
};
|
|
||||||
public override string[] LegacyUrls => new[]
|
|
||||||
{
|
|
||||||
"https://audiobookbay.la/",
|
|
||||||
"http://audiobookbay.net/",
|
|
||||||
"https://audiobookbay.unblockit.tv/",
|
|
||||||
"http://audiobookbay.nl/",
|
|
||||||
"http://audiobookbay.ws/",
|
|
||||||
"https://audiobookbay.unblockit.how/",
|
|
||||||
"https://audiobookbay.unblockit.cam/",
|
|
||||||
"https://audiobookbay.unblockit.biz/",
|
|
||||||
"https://audiobookbay.unblockit.day/",
|
|
||||||
"https://audiobookbay.unblockit.llc/",
|
|
||||||
"https://audiobookbay.unblockit.blue/",
|
|
||||||
"https://audiobookbay.unblockit.name/",
|
|
||||||
"http://audiobookbay.fi/",
|
|
||||||
"http://audiobookbay.se/",
|
|
||||||
"http://audiobookbayabb.com/",
|
|
||||||
"https://audiobookbay.unblockit.ist/",
|
|
||||||
"https://audiobookbay.unblockit.bet/",
|
|
||||||
"https://audiobookbay.unblockit.cat/",
|
|
||||||
"https://audiobookbay.unblockit.nz/",
|
|
||||||
"https://audiobookbay.fi/",
|
|
||||||
"https://audiobookbay.unblockit.page/",
|
|
||||||
"https://audiobookbay.unblockit.pet/",
|
|
||||||
"https://audiobookbay.unblockit.ink/",
|
|
||||||
"https://audiobookbay.unblockit.bio/", // error 502
|
|
||||||
"https://audiobookbay.li/",
|
|
||||||
"https://audiobookbay.se/" // redirects to .is but has invalid CA
|
|
||||||
};
|
|
||||||
public override string Description => "AudioBook Bay (ABB) is a public Torrent Tracker for AUDIOBOOKS";
|
|
||||||
public override string Language => "en-US";
|
|
||||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Public;
|
|
||||||
public override int PageSize => 15;
|
|
||||||
public override TimeSpan RateLimit => TimeSpan.FromSeconds(5);
|
|
||||||
public override IndexerCapabilities Capabilities => SetCapabilities();
|
|
||||||
|
|
||||||
public AudioBookBay(IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
|
|
||||||
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public override IIndexerRequestGenerator GetRequestGenerator()
|
|
||||||
{
|
|
||||||
return new AudioBookBayRequestGenerator(Settings);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override IParseIndexerResponse GetParser()
|
|
||||||
{
|
|
||||||
return new AudioBookBayParser(Settings, Capabilities.Categories);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async Task<byte[]> Download(Uri link)
|
|
||||||
{
|
|
||||||
var request = new HttpRequestBuilder(link.ToString())
|
|
||||||
.SetCookies(GetCookies() ?? new Dictionary<string, string>())
|
|
||||||
.Accept(HttpAccept.Html)
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
var response = await _httpClient.ExecuteProxiedAsync(request, Definition);
|
|
||||||
|
|
||||||
var parser = new HtmlParser();
|
|
||||||
using var dom = parser.ParseDocument(response.Content);
|
|
||||||
|
|
||||||
var hash = dom.QuerySelector("td:contains(\"Info Hash:\") ~ td")?.TextContent.Trim();
|
|
||||||
if (hash == null)
|
|
||||||
{
|
|
||||||
throw new Exception($"Failed to fetch hash from {link}");
|
|
||||||
}
|
|
||||||
|
|
||||||
var title = dom.QuerySelector("div.postTitle h1")?.TextContent.Trim();
|
|
||||||
if (title == null)
|
|
||||||
{
|
|
||||||
throw new Exception($"Failed to fetch title from {link}");
|
|
||||||
}
|
|
||||||
|
|
||||||
title = StringUtil.MakeValidFileName(title, '_', false);
|
|
||||||
|
|
||||||
var magnet = MagnetLinkBuilder.BuildPublicMagnetLink(hash, title);
|
|
||||||
|
|
||||||
return await base.Download(new Uri(magnet));
|
|
||||||
}
|
|
||||||
|
|
||||||
private IndexerCapabilities SetCapabilities()
|
|
||||||
{
|
|
||||||
var caps = new IndexerCapabilities
|
|
||||||
{
|
|
||||||
BookSearchParams = new List<BookSearchParam>
|
|
||||||
{
|
|
||||||
BookSearchParam.Q
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.AudioAudiobook);
|
|
||||||
|
|
||||||
return caps;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class AudioBookBayRequestGenerator : IIndexerRequestGenerator
|
|
||||||
{
|
|
||||||
private readonly NoAuthTorrentBaseSettings _settings;
|
|
||||||
|
|
||||||
public AudioBookBayRequestGenerator(NoAuthTorrentBaseSettings settings)
|
|
||||||
{
|
|
||||||
_settings = settings;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
|
|
||||||
{
|
|
||||||
return new IndexerPageableRequestChain();
|
|
||||||
}
|
|
||||||
|
|
||||||
public IndexerPageableRequestChain GetSearchRequests(MusicSearchCriteria searchCriteria)
|
|
||||||
{
|
|
||||||
return new IndexerPageableRequestChain();
|
|
||||||
}
|
|
||||||
|
|
||||||
public IndexerPageableRequestChain GetSearchRequests(TvSearchCriteria searchCriteria)
|
|
||||||
{
|
|
||||||
return new IndexerPageableRequestChain();
|
|
||||||
}
|
|
||||||
|
|
||||||
public IndexerPageableRequestChain GetSearchRequests(BookSearchCriteria searchCriteria)
|
|
||||||
{
|
|
||||||
var pageableRequests = new IndexerPageableRequestChain();
|
|
||||||
|
|
||||||
pageableRequests.Add(GetPagedRequests($"{searchCriteria.SanitizedSearchTerm}"));
|
|
||||||
|
|
||||||
return pageableRequests;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IndexerPageableRequestChain GetSearchRequests(BasicSearchCriteria searchCriteria)
|
|
||||||
{
|
|
||||||
var pageableRequests = new IndexerPageableRequestChain();
|
|
||||||
|
|
||||||
pageableRequests.Add(GetPagedRequests($"{searchCriteria.SanitizedSearchTerm}"));
|
|
||||||
|
|
||||||
return pageableRequests;
|
|
||||||
}
|
|
||||||
|
|
||||||
private IEnumerable<IndexerRequest> GetPagedRequests(string term)
|
|
||||||
{
|
|
||||||
var searchUrl = _settings.BaseUrl;
|
|
||||||
|
|
||||||
var parameters = new NameValueCollection();
|
|
||||||
|
|
||||||
term = Regex.Replace(term, @"[\W]+", " ").Trim();
|
|
||||||
|
|
||||||
if (term.IsNotNullOrWhiteSpace())
|
|
||||||
{
|
|
||||||
parameters.Set("s", term);
|
|
||||||
parameters.Set("tt", "1");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parameters.Count > 0)
|
|
||||||
{
|
|
||||||
searchUrl += $"?{parameters.GetQueryString()}";
|
|
||||||
}
|
|
||||||
|
|
||||||
yield return new IndexerRequest(new UriBuilder(searchUrl) { Path = "/" }.Uri.AbsoluteUri, HttpAccept.Html);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Func<IDictionary<string, string>> GetCookies { get; set; }
|
|
||||||
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class AudioBookBayParser : IParseIndexerResponse
|
|
||||||
{
|
|
||||||
private readonly NoAuthTorrentBaseSettings _settings;
|
|
||||||
private readonly IndexerCapabilitiesCategories _categories;
|
|
||||||
|
|
||||||
public AudioBookBayParser(NoAuthTorrentBaseSettings settings, IndexerCapabilitiesCategories categories)
|
|
||||||
{
|
|
||||||
_settings = settings;
|
|
||||||
_categories = categories;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
|
|
||||||
{
|
|
||||||
var releaseInfos = new List<ReleaseInfo>();
|
|
||||||
|
|
||||||
using var doc = ParseHtmlDocument(indexerResponse.Content);
|
|
||||||
|
|
||||||
var rows = doc.QuerySelectorAll("div.post:has(div[class=\"postTitle\"])");
|
|
||||||
foreach (var row in rows)
|
|
||||||
{
|
|
||||||
var infoUrl = _settings.BaseUrl + row.QuerySelector("div.postTitle h2 a")?.GetAttribute("href")?.Trim().TrimStart('/');
|
|
||||||
|
|
||||||
var title = row.QuerySelector("div.postTitle")?.TextContent.Trim();
|
|
||||||
|
|
||||||
var infoString = row.QuerySelector("div.postContent")?.TextContent.Trim() ?? string.Empty;
|
|
||||||
|
|
||||||
var matchFormat = Regex.Match(infoString, @"Format: (.+) \/", RegexOptions.IgnoreCase);
|
|
||||||
if (matchFormat.Groups[1].Success && matchFormat.Groups[1].Value.Length > 0 && matchFormat.Groups[1].Value != "?")
|
|
||||||
{
|
|
||||||
title += $" [{matchFormat.Groups[1].Value.Trim()}]";
|
|
||||||
}
|
|
||||||
|
|
||||||
var matchBitrate = Regex.Match(infoString, @"Bitrate: (.+)File", RegexOptions.IgnoreCase);
|
|
||||||
if (matchBitrate.Groups[1].Success && matchBitrate.Groups[1].Value.Length > 0 && matchBitrate.Groups[1].Value != "?")
|
|
||||||
{
|
|
||||||
title += $" [{matchBitrate.Groups[1].Value.Trim()}]";
|
|
||||||
}
|
|
||||||
|
|
||||||
var matchSize = Regex.Match(infoString, @"File Size: (.+?)s?$", RegexOptions.IgnoreCase);
|
|
||||||
var size = matchSize.Groups[1].Success ? ParseUtil.GetBytes(matchSize.Groups[1].Value) : 0;
|
|
||||||
|
|
||||||
var matchDateAdded = Regex.Match(infoString, @"Posted: (\d{1,2} \D{3} \d{4})", RegexOptions.IgnoreCase);
|
|
||||||
var publishDate = matchDateAdded.Groups[1].Success && DateTime.TryParseExact(matchDateAdded.Groups[1].Value, "d MMM yyyy", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out var parsedDate) ? parsedDate : DateTime.Now;
|
|
||||||
|
|
||||||
var postInfo = row.QuerySelector("div.postInfo")?.FirstChild?.TextContent.Trim().Replace("\xA0", ";") ?? string.Empty;
|
|
||||||
var matchCategory = Regex.Match(postInfo, @"Category: (.+)$", RegexOptions.IgnoreCase);
|
|
||||||
var genres = matchCategory.Groups[1].Success ? matchCategory.Groups[1].Value.Split(';', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries).ToList() : new List<string>();
|
|
||||||
|
|
||||||
var release = new TorrentInfo
|
|
||||||
{
|
|
||||||
Guid = infoUrl,
|
|
||||||
InfoUrl = infoUrl,
|
|
||||||
DownloadUrl = infoUrl,
|
|
||||||
Title = CleanTitle(title),
|
|
||||||
Categories = new List<IndexerCategory> { NewznabStandardCategory.AudioAudiobook },
|
|
||||||
Size = size,
|
|
||||||
Seeders = 1,
|
|
||||||
Peers = 1,
|
|
||||||
PublishDate = publishDate,
|
|
||||||
DownloadVolumeFactor = 0,
|
|
||||||
UploadVolumeFactor = 1,
|
|
||||||
Genres = genres
|
|
||||||
};
|
|
||||||
|
|
||||||
var cover = row.QuerySelector("img[src]")?.GetAttribute("src")?.Trim();
|
|
||||||
if (!string.IsNullOrEmpty(cover))
|
|
||||||
{
|
|
||||||
release.PosterUrl = cover.StartsWith("http") ? cover : _settings.BaseUrl + cover;
|
|
||||||
}
|
|
||||||
|
|
||||||
releaseInfos.Add(release);
|
|
||||||
}
|
|
||||||
|
|
||||||
return releaseInfos;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static IHtmlDocument ParseHtmlDocument(string response)
|
|
||||||
{
|
|
||||||
var parser = new HtmlParser();
|
|
||||||
var doc = parser.ParseDocument(response);
|
|
||||||
|
|
||||||
var hidden = doc.QuerySelectorAll("div.post.re-ab");
|
|
||||||
foreach (var element in hidden)
|
|
||||||
{
|
|
||||||
var body = doc.CreateElement("div");
|
|
||||||
body.ClassList.Add("post");
|
|
||||||
body.InnerHtml = Encoding.UTF8.GetString(Convert.FromBase64String(element.TextContent));
|
|
||||||
element.Parent.ReplaceChild(body, element);
|
|
||||||
}
|
|
||||||
|
|
||||||
return doc;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string CleanTitle(string title)
|
|
||||||
{
|
|
||||||
title = Regex.Replace(title, @"[\u0000-\u0008\u000A-\u001F\u0100-\uFFFF]", string.Empty, RegexOptions.Compiled);
|
|
||||||
title = Regex.Replace(title, @"\s+", " ", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
|
||||||
|
|
||||||
return title.Trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
|
|
||||||
}
|
|
@ -1,315 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Collections.Specialized;
|
|
||||||
using System.Net.Http;
|
|
||||||
using System.Text;
|
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using AngleSharp.Dom;
|
|
||||||
using AngleSharp.Html.Parser;
|
|
||||||
using NLog;
|
|
||||||
using NzbDrone.Common.Extensions;
|
|
||||||
using NzbDrone.Common.Http;
|
|
||||||
using NzbDrone.Core.Configuration;
|
|
||||||
using NzbDrone.Core.Indexers.Exceptions;
|
|
||||||
using NzbDrone.Core.Indexers.Settings;
|
|
||||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
|
||||||
using NzbDrone.Core.Messaging.Events;
|
|
||||||
using NzbDrone.Core.Parser;
|
|
||||||
using NzbDrone.Core.Parser.Model;
|
|
||||||
|
|
||||||
namespace NzbDrone.Core.Indexers.Definitions
|
|
||||||
{
|
|
||||||
[Obsolete("Site has shutdown")]
|
|
||||||
public class BB : TorrentIndexerBase<UserPassTorrentBaseSettings>
|
|
||||||
{
|
|
||||||
public override string Name => "BB";
|
|
||||||
public override string[] IndexerUrls => new[] { Base64Extensions.FromBase64("aHR0cHM6Ly9iYWNvbmJpdHMub3JnLw==") };
|
|
||||||
private string LoginUrl => Settings.BaseUrl + "login.php";
|
|
||||||
public override string Description => "BB is a Private Torrent Tracker for 0DAY / GENERAL";
|
|
||||||
public override string Language => "en-US";
|
|
||||||
public override Encoding Encoding => Encoding.UTF8;
|
|
||||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
|
|
||||||
public override IndexerCapabilities Capabilities => SetCapabilities();
|
|
||||||
|
|
||||||
public BB(IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
|
|
||||||
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public override IIndexerRequestGenerator GetRequestGenerator()
|
|
||||||
{
|
|
||||||
return new BBRequestGenerator { Settings = Settings, Capabilities = Capabilities };
|
|
||||||
}
|
|
||||||
|
|
||||||
public override IParseIndexerResponse GetParser()
|
|
||||||
{
|
|
||||||
return new BBParser(Settings, Capabilities.Categories);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override async Task DoLogin()
|
|
||||||
{
|
|
||||||
var requestBuilder = new HttpRequestBuilder(LoginUrl)
|
|
||||||
{
|
|
||||||
LogResponseContent = true,
|
|
||||||
AllowAutoRedirect = true,
|
|
||||||
Method = HttpMethod.Post
|
|
||||||
};
|
|
||||||
|
|
||||||
var cookies = Cookies;
|
|
||||||
Cookies = null;
|
|
||||||
|
|
||||||
var authLoginRequest = requestBuilder
|
|
||||||
.AddFormParameter("username", Settings.Username)
|
|
||||||
.AddFormParameter("password", Settings.Password)
|
|
||||||
.AddFormParameter("keeplogged", "1")
|
|
||||||
.AddFormParameter("login", "Log+In!")
|
|
||||||
.SetHeader("Content-Type", "application/x-www-form-urlencoded")
|
|
||||||
.SetHeader("Referer", LoginUrl)
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
var response = await ExecuteAuth(authLoginRequest);
|
|
||||||
|
|
||||||
if (CheckIfLoginNeeded(response))
|
|
||||||
{
|
|
||||||
var parser = new HtmlParser();
|
|
||||||
using var dom = parser.ParseDocument(response.Content);
|
|
||||||
var messageEl = dom.QuerySelectorAll("#loginform");
|
|
||||||
var messages = new List<string>();
|
|
||||||
for (var i = 0; i < 13; i++)
|
|
||||||
{
|
|
||||||
var child = messageEl[0].ChildNodes[i];
|
|
||||||
messages.Add(child.Text().Trim());
|
|
||||||
}
|
|
||||||
|
|
||||||
var message = string.Join(" ", messages);
|
|
||||||
|
|
||||||
throw new IndexerAuthException(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
cookies = response.GetCookies();
|
|
||||||
UpdateCookies(cookies, DateTime.Now.AddDays(30));
|
|
||||||
|
|
||||||
_logger.Debug("BB authentication succeeded.");
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override bool CheckIfLoginNeeded(HttpResponse httpResponse)
|
|
||||||
{
|
|
||||||
return !httpResponse.Content.Contains("logout.php");
|
|
||||||
}
|
|
||||||
|
|
||||||
private 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
|
|
||||||
},
|
|
||||||
BookSearchParams = new List<BookSearchParam>
|
|
||||||
{
|
|
||||||
BookSearchParam.Q
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.Audio);
|
|
||||||
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.AudioMP3);
|
|
||||||
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.AudioLossless);
|
|
||||||
caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.PC);
|
|
||||||
caps.Categories.AddCategoryMapping(3, NewznabStandardCategory.BooksEBook);
|
|
||||||
caps.Categories.AddCategoryMapping(4, NewznabStandardCategory.AudioAudiobook);
|
|
||||||
caps.Categories.AddCategoryMapping(5, NewznabStandardCategory.Other);
|
|
||||||
caps.Categories.AddCategoryMapping(6, NewznabStandardCategory.BooksMags);
|
|
||||||
caps.Categories.AddCategoryMapping(7, NewznabStandardCategory.BooksComics);
|
|
||||||
caps.Categories.AddCategoryMapping(8, NewznabStandardCategory.TVAnime);
|
|
||||||
caps.Categories.AddCategoryMapping(9, NewznabStandardCategory.Movies);
|
|
||||||
caps.Categories.AddCategoryMapping(10, NewznabStandardCategory.TVHD);
|
|
||||||
caps.Categories.AddCategoryMapping(10, NewznabStandardCategory.TVSD);
|
|
||||||
caps.Categories.AddCategoryMapping(10, NewznabStandardCategory.TV);
|
|
||||||
caps.Categories.AddCategoryMapping(11, NewznabStandardCategory.PCGames);
|
|
||||||
caps.Categories.AddCategoryMapping(12, NewznabStandardCategory.Console);
|
|
||||||
caps.Categories.AddCategoryMapping(13, NewznabStandardCategory.Other);
|
|
||||||
caps.Categories.AddCategoryMapping(14, NewznabStandardCategory.Other);
|
|
||||||
|
|
||||||
return caps;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class BBRequestGenerator : IIndexerRequestGenerator
|
|
||||||
{
|
|
||||||
public UserPassTorrentBaseSettings Settings { get; set; }
|
|
||||||
public IndexerCapabilities Capabilities { get; set; }
|
|
||||||
|
|
||||||
private IEnumerable<IndexerRequest> GetPagedRequests(string term, int[] categories)
|
|
||||||
{
|
|
||||||
var searchUrl = string.Format("{0}/torrents.php", Settings.BaseUrl.TrimEnd('/'));
|
|
||||||
|
|
||||||
// TODO: IMDB search is available but it requires to parse the details page
|
|
||||||
var qc = new NameValueCollection
|
|
||||||
{
|
|
||||||
{ "order_by", "s3" },
|
|
||||||
{ "order_way", "desc" },
|
|
||||||
{ "disablegrouping", "1" },
|
|
||||||
{ "searchtags", "" },
|
|
||||||
{ "tags_type", "0" },
|
|
||||||
{ "action", "basic" },
|
|
||||||
{ "searchstr", term.Replace(".", " ") }
|
|
||||||
};
|
|
||||||
|
|
||||||
var catList = Capabilities.Categories.MapTorznabCapsToTrackers(categories);
|
|
||||||
|
|
||||||
foreach (var cat in catList)
|
|
||||||
{
|
|
||||||
qc.Add($"filter_cat[{cat}]", "1");
|
|
||||||
}
|
|
||||||
|
|
||||||
searchUrl = searchUrl + "?" + qc.GetQueryString();
|
|
||||||
|
|
||||||
var request = new IndexerRequest(searchUrl, HttpAccept.Html);
|
|
||||||
|
|
||||||
yield return request;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
|
|
||||||
{
|
|
||||||
var pageableRequests = new IndexerPageableRequestChain();
|
|
||||||
|
|
||||||
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories));
|
|
||||||
|
|
||||||
return pageableRequests;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IndexerPageableRequestChain GetSearchRequests(MusicSearchCriteria searchCriteria)
|
|
||||||
{
|
|
||||||
var pageableRequests = new IndexerPageableRequestChain();
|
|
||||||
|
|
||||||
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories));
|
|
||||||
|
|
||||||
return pageableRequests;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IndexerPageableRequestChain GetSearchRequests(TvSearchCriteria searchCriteria)
|
|
||||||
{
|
|
||||||
var pageableRequests = new IndexerPageableRequestChain();
|
|
||||||
|
|
||||||
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedTvSearchString), searchCriteria.Categories));
|
|
||||||
|
|
||||||
return pageableRequests;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IndexerPageableRequestChain GetSearchRequests(BookSearchCriteria searchCriteria)
|
|
||||||
{
|
|
||||||
var pageableRequests = new IndexerPageableRequestChain();
|
|
||||||
|
|
||||||
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories));
|
|
||||||
|
|
||||||
return pageableRequests;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IndexerPageableRequestChain GetSearchRequests(BasicSearchCriteria searchCriteria)
|
|
||||||
{
|
|
||||||
var pageableRequests = new IndexerPageableRequestChain();
|
|
||||||
|
|
||||||
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories));
|
|
||||||
|
|
||||||
return pageableRequests;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Func<IDictionary<string, string>> GetCookies { get; set; }
|
|
||||||
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class BBParser : IParseIndexerResponse
|
|
||||||
{
|
|
||||||
private readonly UserPassTorrentBaseSettings _settings;
|
|
||||||
private readonly IndexerCapabilitiesCategories _categories;
|
|
||||||
|
|
||||||
public BBParser(UserPassTorrentBaseSettings settings, IndexerCapabilitiesCategories categories)
|
|
||||||
{
|
|
||||||
_settings = settings;
|
|
||||||
_categories = categories;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
|
|
||||||
{
|
|
||||||
var torrentInfos = new List<ReleaseInfo>();
|
|
||||||
|
|
||||||
var parser = new HtmlParser();
|
|
||||||
using var dom = parser.ParseDocument(indexerResponse.Content);
|
|
||||||
var rows = dom.QuerySelectorAll("#torrent_table > tbody > tr.torrent");
|
|
||||||
|
|
||||||
foreach (var row in rows)
|
|
||||||
{
|
|
||||||
var release = new TorrentInfo();
|
|
||||||
|
|
||||||
release.MinimumRatio = 1;
|
|
||||||
release.MinimumSeedTime = 172800; // 48 hours
|
|
||||||
|
|
||||||
var catStr = row.Children[0].FirstElementChild.GetAttribute("href").Split(new[] { '[', ']' })[1];
|
|
||||||
release.Categories = _categories.MapTrackerCatToNewznab(catStr);
|
|
||||||
|
|
||||||
var qDetails = row.Children[1].QuerySelector("a[title='View Torrent']");
|
|
||||||
release.InfoUrl = _settings.BaseUrl + qDetails.GetAttribute("href");
|
|
||||||
release.Guid = release.InfoUrl;
|
|
||||||
|
|
||||||
var qDownload = row.Children[1].QuerySelector("a[title='Download']");
|
|
||||||
release.DownloadUrl = _settings.BaseUrl + qDownload.GetAttribute("href");
|
|
||||||
|
|
||||||
var dateStr = row.Children[3].TextContent.Trim().Replace(" and", "");
|
|
||||||
release.PublishDate = DateTimeUtil.FromTimeAgo(dateStr);
|
|
||||||
|
|
||||||
var sizeStr = row.Children[4].TextContent;
|
|
||||||
release.Size = ParseUtil.GetBytes(sizeStr);
|
|
||||||
|
|
||||||
release.Files = ParseUtil.CoerceInt(row.Children[2].TextContent.Trim());
|
|
||||||
release.Seeders = ParseUtil.CoerceInt(row.Children[7].TextContent.Trim());
|
|
||||||
release.Peers = ParseUtil.CoerceInt(row.Children[8].TextContent.Trim()) + release.Seeders;
|
|
||||||
|
|
||||||
var grabs = row.QuerySelector("td:nth-child(6)").TextContent;
|
|
||||||
release.Grabs = ParseUtil.CoerceInt(grabs);
|
|
||||||
|
|
||||||
if (row.QuerySelector("strong:contains(\"Freeleech!\")") != null)
|
|
||||||
{
|
|
||||||
release.DownloadVolumeFactor = 0;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
release.DownloadVolumeFactor = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
release.UploadVolumeFactor = 1;
|
|
||||||
|
|
||||||
var title = row.QuerySelector("td:nth-child(2)");
|
|
||||||
foreach (var element in title.QuerySelectorAll("span, strong, div, br"))
|
|
||||||
{
|
|
||||||
element.Remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
release.Title = ParseUtil.NormalizeMultiSpaces(title.TextContent.Replace(" - ]", "]"));
|
|
||||||
|
|
||||||
//change "Season #" to "S##" for TV shows
|
|
||||||
if (catStr == "10")
|
|
||||||
{
|
|
||||||
release.Title = Regex.Replace(release.Title,
|
|
||||||
@"Season (\d+)",
|
|
||||||
m => string.Format("S{0:00}",
|
|
||||||
int.Parse(m.Groups[1].Value)));
|
|
||||||
}
|
|
||||||
|
|
||||||
torrentInfos.Add(release);
|
|
||||||
}
|
|
||||||
|
|
||||||
return torrentInfos.ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,310 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Collections.Specialized;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using System.Web;
|
|
||||||
using AngleSharp.Dom;
|
|
||||||
using AngleSharp.Html.Dom;
|
|
||||||
using AngleSharp.Html.Parser;
|
|
||||||
using NLog;
|
|
||||||
using NzbDrone.Common.Http;
|
|
||||||
using NzbDrone.Core.Configuration;
|
|
||||||
using NzbDrone.Core.Indexers.Settings;
|
|
||||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
|
||||||
using NzbDrone.Core.Messaging.Events;
|
|
||||||
using NzbDrone.Core.Parser;
|
|
||||||
using NzbDrone.Core.Parser.Model;
|
|
||||||
|
|
||||||
namespace NzbDrone.Core.Indexers.Definitions;
|
|
||||||
|
|
||||||
[Obsolete("Converted to Torznab")]
|
|
||||||
public class MoreThanTV : TorrentIndexerBase<CookieTorrentBaseSettings>
|
|
||||||
{
|
|
||||||
public override string Name => "MoreThanTV";
|
|
||||||
public override string[] IndexerUrls => new[] { "https://www.morethantv.me/" };
|
|
||||||
public override string Description => "Private torrent tracker for TV / MOVIES";
|
|
||||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
|
|
||||||
public override IndexerCapabilities Capabilities => SetCapabilities();
|
|
||||||
public override bool FollowRedirect => true;
|
|
||||||
|
|
||||||
public MoreThanTV(IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
|
|
||||||
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public override IIndexerRequestGenerator GetRequestGenerator()
|
|
||||||
=> new MoreThanTVRequestGenerator(Settings, Capabilities);
|
|
||||||
|
|
||||||
public override IParseIndexerResponse GetParser()
|
|
||||||
=> new MoreThanTVParser
|
|
||||||
{
|
|
||||||
Settings = Settings
|
|
||||||
};
|
|
||||||
|
|
||||||
private IndexerCapabilities SetCapabilities()
|
|
||||||
{
|
|
||||||
var caps = new IndexerCapabilities
|
|
||||||
{
|
|
||||||
TvSearchParams = new List<TvSearchParam>
|
|
||||||
{
|
|
||||||
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep
|
|
||||||
},
|
|
||||||
MovieSearchParams = new List<MovieSearchParam>
|
|
||||||
{
|
|
||||||
MovieSearchParam.Q
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.Movies, "Movies");
|
|
||||||
caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.TV, "TV");
|
|
||||||
|
|
||||||
return caps;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override IDictionary<string, string> GetCookies()
|
|
||||||
{
|
|
||||||
return CookieUtil.CookieHeaderToDictionary(Settings.Cookie);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class MoreThanTVRequestGenerator : IIndexerRequestGenerator
|
|
||||||
{
|
|
||||||
private CookieTorrentBaseSettings Settings { get; }
|
|
||||||
private IndexerCapabilities Capabilities { get; }
|
|
||||||
|
|
||||||
private NameValueCollection BrowserHeaders { get; }
|
|
||||||
|
|
||||||
public MoreThanTVRequestGenerator(CookieTorrentBaseSettings settings, IndexerCapabilities capabilities)
|
|
||||||
{
|
|
||||||
Settings = settings;
|
|
||||||
Capabilities = capabilities;
|
|
||||||
BrowserHeaders = new NameValueCollection()
|
|
||||||
{
|
|
||||||
{ "referer", settings.BaseUrl },
|
|
||||||
{ "Upgrade-Insecure-Requests", "1" },
|
|
||||||
{ "User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.72 Safari/537.36" }
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
|
|
||||||
=> PerformRequest(searchCriteria);
|
|
||||||
|
|
||||||
public IndexerPageableRequestChain GetSearchRequests(MusicSearchCriteria searchCriteria)
|
|
||||||
=> PerformRequest(searchCriteria);
|
|
||||||
|
|
||||||
public IndexerPageableRequestChain GetSearchRequests(TvSearchCriteria searchCriteria)
|
|
||||||
=> PerformRequest(searchCriteria);
|
|
||||||
|
|
||||||
public IndexerPageableRequestChain GetSearchRequests(BookSearchCriteria searchCriteria)
|
|
||||||
=> PerformRequest(searchCriteria);
|
|
||||||
|
|
||||||
public IndexerPageableRequestChain GetSearchRequests(BasicSearchCriteria searchCriteria)
|
|
||||||
=> PerformRequest(searchCriteria);
|
|
||||||
|
|
||||||
public Func<IDictionary<string, string>> GetCookies { get; set; }
|
|
||||||
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
|
|
||||||
|
|
||||||
private IndexerPageableRequestChain PerformRequest(SearchCriteriaBase query)
|
|
||||||
{
|
|
||||||
var chain = new IndexerPageableRequestChain();
|
|
||||||
|
|
||||||
var requests = new List<IndexerRequest> { new (new HttpRequest(GetTorrentSearchUrl(query)) { Headers = new HttpHeader(BrowserHeaders), AllowAutoRedirect = true }) };
|
|
||||||
|
|
||||||
if (query is TvSearchCriteria tvSearchCriteria)
|
|
||||||
{
|
|
||||||
// Always search for torrent groups (complete seasons) too
|
|
||||||
var seasonRegex = new Regex(@".*\s[Ss]{1}\d{2}([Ee]{1}\d{2,3})?$", RegexOptions.Compiled);
|
|
||||||
var seasonMatch = seasonRegex.Match(query.SanitizedSearchTerm);
|
|
||||||
if (seasonMatch.Success)
|
|
||||||
{
|
|
||||||
var seasonReplaceRegex = new Regex(@"[Ss]{1}\d{2}([Ee]{1}\d{2,3})?", RegexOptions.Compiled);
|
|
||||||
var newSearchQuery = seasonReplaceRegex.Replace(query.SanitizedSearchTerm, $"Season {tvSearchCriteria.Season}");
|
|
||||||
requests.Add(new IndexerRequest(new HttpRequest(GetTorrentSearchUrl(query, newSearchQuery)) { Headers = new HttpHeader(BrowserHeaders), AllowAutoRedirect = true }));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
chain.Add(requests);
|
|
||||||
|
|
||||||
return chain;
|
|
||||||
}
|
|
||||||
|
|
||||||
private string GetTorrentSearchUrl(SearchCriteriaBase query, string overrideSearchTerm = null)
|
|
||||||
{
|
|
||||||
var qc = new NameValueCollection
|
|
||||||
{
|
|
||||||
{ "action", "advanced" },
|
|
||||||
{ "sizetype", "gb" },
|
|
||||||
{ "sizerange", "0.01" },
|
|
||||||
{ "title", overrideSearchTerm ?? GetSearchString(query.SanitizedSearchTerm) }
|
|
||||||
};
|
|
||||||
|
|
||||||
switch (query)
|
|
||||||
{
|
|
||||||
case MovieSearchCriteria:
|
|
||||||
qc.Add("filter_cat[1]", "1"); // HD Movies
|
|
||||||
qc.Add("filter_cat[2]", "1"); // SD Movies
|
|
||||||
break;
|
|
||||||
case TvSearchCriteria:
|
|
||||||
qc.Add("filter_cat[3]", "1"); // HD Episode
|
|
||||||
qc.Add("filter_cat[4]", "1"); // SD Episode
|
|
||||||
qc.Add("filter_cat[5]", "1"); // HD Season
|
|
||||||
qc.Add("filter_cat[6]", "1"); // SD Season
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $"{Settings.BaseUrl}torrents/browse?{qc.GetQueryString()}";
|
|
||||||
}
|
|
||||||
|
|
||||||
private string GetSearchString(string input)
|
|
||||||
{
|
|
||||||
input = input.Replace("Marvels", "Marvel"); // strip 's for better results
|
|
||||||
var regex = new Regex(@"(S\d{2})$", RegexOptions.Compiled);
|
|
||||||
return regex.Replace(input, "$1*"); // If we're just seaching for a season (no episode) append an * to include all episodes of that season.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class MoreThanTVParser : IParseIndexerResponse
|
|
||||||
{
|
|
||||||
public CookieTorrentBaseSettings Settings { get; init; }
|
|
||||||
|
|
||||||
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
|
|
||||||
{
|
|
||||||
var releases = new List<ReleaseInfo>();
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var parser = new HtmlParser();
|
|
||||||
using var document = parser.ParseDocument(indexerResponse.Content);
|
|
||||||
var torrents = document.QuerySelectorAll("#torrent_table > tbody > tr.torrent");
|
|
||||||
var movies = new[] { "movie" };
|
|
||||||
var tv = new[] { "season", "episode" };
|
|
||||||
|
|
||||||
// Loop through all torrents checking for groups
|
|
||||||
foreach (var torrent in torrents)
|
|
||||||
{
|
|
||||||
// Parse required data
|
|
||||||
var downloadAnchor = torrent.QuerySelector("span a[href^=\"/torrents.php?action=download\"]");
|
|
||||||
|
|
||||||
if (downloadAnchor == null)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var title = downloadAnchor.ParentElement.ParentElement.ParentElement.QuerySelector("a[class=\"overlay_torrent\"]").TextContent.Trim();
|
|
||||||
title = CleanUpTitle(title);
|
|
||||||
|
|
||||||
var category = torrent.QuerySelector(".cats_col div").GetAttribute("title");
|
|
||||||
|
|
||||||
// default to Other
|
|
||||||
var indexerCategory = NewznabStandardCategory.Other;
|
|
||||||
|
|
||||||
if (movies.Any(category.Contains))
|
|
||||||
{
|
|
||||||
indexerCategory = NewznabStandardCategory.Movies;
|
|
||||||
}
|
|
||||||
else if (tv.Any(category.Contains))
|
|
||||||
{
|
|
||||||
indexerCategory = NewznabStandardCategory.TV;
|
|
||||||
}
|
|
||||||
|
|
||||||
releases.Add(GetReleaseInfo(torrent, downloadAnchor, title, indexerCategory));
|
|
||||||
}
|
|
||||||
|
|
||||||
return releases;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
throw new Exception("Error while parsing torrent response", ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gather Release info from torrent table. Target using css
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="row"></param>
|
|
||||||
/// <param name="downloadAnchor"></param>
|
|
||||||
/// <param name="title"></param>
|
|
||||||
/// <param name="category"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
private ReleaseInfo GetReleaseInfo(IElement row, IElement downloadAnchor, string title, IndexerCategory category)
|
|
||||||
{
|
|
||||||
// count from bottom
|
|
||||||
const int FILES_COL = 7;
|
|
||||||
/*const int COMMENTS_COL = 7;*/
|
|
||||||
const int DATE_COL = 6;
|
|
||||||
const int FILESIZE_COL = 5;
|
|
||||||
const int SNATCHED_COL = 4;
|
|
||||||
const int SEEDS_COL = 3;
|
|
||||||
const int LEECHERS_COL = 2;
|
|
||||||
/*const int USER_COL = 1;*/
|
|
||||||
|
|
||||||
var downloadAnchorHref = (downloadAnchor as IHtmlAnchorElement).Href;
|
|
||||||
var queryParams = HttpUtility.ParseQueryString(downloadAnchorHref, Encoding.UTF8);
|
|
||||||
var torrentId = queryParams["id"];
|
|
||||||
|
|
||||||
var qFiles = row.QuerySelector("td:nth-last-child(" + FILES_COL + ")").TextContent;
|
|
||||||
|
|
||||||
var fileCount = ParseUtil.CoerceInt(qFiles);
|
|
||||||
var qPublishDate = row.QuerySelector("td:nth-last-child(" + DATE_COL + ") .time").Attributes["title"].Value;
|
|
||||||
var publishDate = DateTime.ParseExact(qPublishDate, "MMM dd yyyy, HH:mm", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal).ToLocalTime();
|
|
||||||
var qPoster = row.QuerySelector("div.tp-banner img")?.GetAttribute("src");
|
|
||||||
var poster = (qPoster != null && !qPoster.Contains("caticons")) ? qPoster : null;
|
|
||||||
var description = row.QuerySelector("div.tags")?.TextContent.Trim();
|
|
||||||
var fileSize = row.QuerySelector("td:nth-last-child(" + FILESIZE_COL + ")").TextContent.Trim();
|
|
||||||
var snatched = row.QuerySelector("td:nth-last-child(" + SNATCHED_COL + ")").TextContent.Trim();
|
|
||||||
var seeds = row.QuerySelector("td:nth-last-child(" + SEEDS_COL + ")").TextContent.Trim();
|
|
||||||
var leechs = row.QuerySelector("td:nth-last-child(" + LEECHERS_COL + ")").TextContent.Trim();
|
|
||||||
|
|
||||||
if (fileSize.Length <= 0 || snatched.Length <= 0 || seeds.Length <= 0 || leechs.Length <= 0)
|
|
||||||
{
|
|
||||||
// Size (xx.xx GB[ (Max)]) Snatches (xx) Seeders (xx) Leechers (xx)
|
|
||||||
throw new Exception($"We expected 4 torrent datas.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var detailUrl = $"{Settings.BaseUrl}details.php";
|
|
||||||
|
|
||||||
var size = ParseUtil.GetBytes(fileSize);
|
|
||||||
var grabs = int.Parse(snatched, NumberStyles.AllowThousands, CultureInfo.InvariantCulture);
|
|
||||||
var seeders = int.Parse(seeds, NumberStyles.AllowThousands, CultureInfo.InvariantCulture);
|
|
||||||
var leechers = int.Parse(leechs, NumberStyles.AllowThousands, CultureInfo.InvariantCulture);
|
|
||||||
var detailsUrl = $"{detailUrl}?torrentid={torrentId}";
|
|
||||||
var downloadUrl = $"{detailUrl}?action=download&id={torrentId}";
|
|
||||||
var categories = new List<IndexerCategory> { category };
|
|
||||||
|
|
||||||
return new TorrentInfo
|
|
||||||
{
|
|
||||||
Title = title,
|
|
||||||
Categories = categories,
|
|
||||||
DownloadUrl = downloadUrl,
|
|
||||||
PublishDate = publishDate,
|
|
||||||
PosterUrl = poster,
|
|
||||||
Description = description,
|
|
||||||
Seeders = seeders,
|
|
||||||
Peers = seeders + leechers,
|
|
||||||
Files = fileCount,
|
|
||||||
Size = size,
|
|
||||||
Grabs = grabs,
|
|
||||||
Guid = downloadUrl,
|
|
||||||
InfoUrl = detailsUrl,
|
|
||||||
DownloadVolumeFactor = 0, // ratioless tracker
|
|
||||||
UploadVolumeFactor = 1
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Clean Up any title stuff
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="title"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
private string CleanUpTitle(string title)
|
|
||||||
{
|
|
||||||
return title
|
|
||||||
.Replace(".", " ")
|
|
||||||
.Replace("4K", "2160p"); // sonarr cleanup
|
|
||||||
}
|
|
||||||
|
|
||||||
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
|
|
||||||
}
|
|
@ -1,292 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Collections.Specialized;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Net.Http;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using AngleSharp.Html.Parser;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using NLog;
|
|
||||||
using NzbDrone.Common.Extensions;
|
|
||||||
using NzbDrone.Common.Http;
|
|
||||||
using NzbDrone.Core.Configuration;
|
|
||||||
using NzbDrone.Core.Indexers.Exceptions;
|
|
||||||
using NzbDrone.Core.Indexers.Settings;
|
|
||||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
|
||||||
using NzbDrone.Core.Messaging.Events;
|
|
||||||
using NzbDrone.Core.Parser;
|
|
||||||
using NzbDrone.Core.Parser.Model;
|
|
||||||
|
|
||||||
namespace NzbDrone.Core.Indexers.Definitions;
|
|
||||||
[Obsolete("PirateTheNet has shutdown 2023-10-14")]
|
|
||||||
public class PirateTheNet : TorrentIndexerBase<UserPassTorrentBaseSettings>
|
|
||||||
{
|
|
||||||
public override string Name => "PirateTheNet";
|
|
||||||
public override string[] IndexerUrls => new[] { "https://piratethenet.org/" };
|
|
||||||
public override string[] LegacyUrls => new[] { "http://piratethenet.org/" };
|
|
||||||
public override string Description => "PirateTheNet (PTN) is a ratioless movie tracker.";
|
|
||||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
|
|
||||||
public override IndexerCapabilities Capabilities => SetCapabilities();
|
|
||||||
private string LoginUrl => Settings.BaseUrl + "takelogin.php";
|
|
||||||
private string CaptchaUrl => Settings.BaseUrl + "simpleCaptcha.php?numImages=1";
|
|
||||||
|
|
||||||
public PirateTheNet(IIndexerHttpClient httpClient,
|
|
||||||
IEventAggregator eventAggregator,
|
|
||||||
IIndexerStatusService indexerStatusService,
|
|
||||||
IConfigService configService,
|
|
||||||
Logger logger)
|
|
||||||
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public override IIndexerRequestGenerator GetRequestGenerator()
|
|
||||||
{
|
|
||||||
return new PirateTheNetRequestGenerator(Settings, Capabilities);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override IParseIndexerResponse GetParser()
|
|
||||||
{
|
|
||||||
return new PirateTheNetParser(Settings, Capabilities.Categories);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override async Task DoLogin()
|
|
||||||
{
|
|
||||||
var captchaPage = await ExecuteAuth(new HttpRequest(CaptchaUrl));
|
|
||||||
|
|
||||||
var captchaResponse = JsonConvert.DeserializeAnonymousType(captchaPage.Content, new
|
|
||||||
{
|
|
||||||
images = new[] { new { hash = string.Empty } }
|
|
||||||
});
|
|
||||||
|
|
||||||
var requestBuilder = new HttpRequestBuilder(LoginUrl)
|
|
||||||
{
|
|
||||||
LogResponseContent = true,
|
|
||||||
AllowAutoRedirect = true,
|
|
||||||
Method = HttpMethod.Post
|
|
||||||
};
|
|
||||||
|
|
||||||
var authLoginRequest = requestBuilder
|
|
||||||
.SetCookies(captchaPage.GetCookies())
|
|
||||||
.AddFormParameter("username", Settings.Username)
|
|
||||||
.AddFormParameter("password", Settings.Password)
|
|
||||||
.AddFormParameter("captchaSelection", captchaResponse.images[0].hash)
|
|
||||||
.SetHeader("Content-Type", "application/x-www-form-urlencoded")
|
|
||||||
.SetHeader("Referer", LoginUrl)
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
var response = await ExecuteAuth(authLoginRequest);
|
|
||||||
|
|
||||||
if (CheckIfLoginNeeded(response))
|
|
||||||
{
|
|
||||||
throw new IndexerAuthException("Login Failed.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var cookies = response.GetCookies();
|
|
||||||
UpdateCookies(cookies, DateTime.Now.AddDays(30));
|
|
||||||
|
|
||||||
_logger.Debug("Authentication succeeded.");
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override bool CheckIfLoginNeeded(HttpResponse httpResponse)
|
|
||||||
{
|
|
||||||
return !httpResponse.Content.Contains("logout.php");
|
|
||||||
}
|
|
||||||
|
|
||||||
private IndexerCapabilities SetCapabilities()
|
|
||||||
{
|
|
||||||
var caps = new IndexerCapabilities
|
|
||||||
{
|
|
||||||
MovieSearchParams = new List<MovieSearchParam>
|
|
||||||
{
|
|
||||||
MovieSearchParam.Q, MovieSearchParam.ImdbId
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
caps.Categories.AddCategoryMapping("1080P", NewznabStandardCategory.MoviesHD, "1080P");
|
|
||||||
caps.Categories.AddCategoryMapping("2160P", NewznabStandardCategory.MoviesHD, "2160P");
|
|
||||||
caps.Categories.AddCategoryMapping("720P", NewznabStandardCategory.MoviesHD, "720P");
|
|
||||||
caps.Categories.AddCategoryMapping("BDRip", NewznabStandardCategory.MoviesSD, "BDRip");
|
|
||||||
caps.Categories.AddCategoryMapping("BluRay", NewznabStandardCategory.MoviesBluRay, "BluRay");
|
|
||||||
caps.Categories.AddCategoryMapping("BRRip", NewznabStandardCategory.MoviesSD, "BRRip");
|
|
||||||
caps.Categories.AddCategoryMapping("DVDR", NewznabStandardCategory.MoviesDVD, "DVDR");
|
|
||||||
caps.Categories.AddCategoryMapping("DVDRip", NewznabStandardCategory.MoviesSD, "DVDRip");
|
|
||||||
caps.Categories.AddCategoryMapping("FLAC", NewznabStandardCategory.AudioLossless, "FLAC OST");
|
|
||||||
caps.Categories.AddCategoryMapping("MP3", NewznabStandardCategory.AudioMP3, "MP3 OST");
|
|
||||||
caps.Categories.AddCategoryMapping("MP4", NewznabStandardCategory.MoviesOther, "MP4");
|
|
||||||
caps.Categories.AddCategoryMapping("Packs", NewznabStandardCategory.MoviesOther, "Packs");
|
|
||||||
caps.Categories.AddCategoryMapping("R5", NewznabStandardCategory.MoviesDVD, "R5 / SCR");
|
|
||||||
caps.Categories.AddCategoryMapping("Remux", NewznabStandardCategory.MoviesOther, "Remux");
|
|
||||||
caps.Categories.AddCategoryMapping("TVRip", NewznabStandardCategory.MoviesOther, "TVRip");
|
|
||||||
caps.Categories.AddCategoryMapping("WebRip", NewznabStandardCategory.MoviesWEBDL, "WebRip");
|
|
||||||
|
|
||||||
return caps;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class PirateTheNetRequestGenerator : IIndexerRequestGenerator
|
|
||||||
{
|
|
||||||
private readonly UserPassTorrentBaseSettings _settings;
|
|
||||||
private readonly IndexerCapabilities _capabilities;
|
|
||||||
|
|
||||||
public PirateTheNetRequestGenerator(UserPassTorrentBaseSettings settings, IndexerCapabilities capabilities)
|
|
||||||
{
|
|
||||||
_settings = settings;
|
|
||||||
_capabilities = capabilities;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
|
|
||||||
{
|
|
||||||
var pageableRequests = new IndexerPageableRequestChain();
|
|
||||||
|
|
||||||
pageableRequests.Add(GetPagedRequests($"{searchCriteria.SanitizedSearchTerm}", searchCriteria.Categories, searchCriteria.FullImdbId));
|
|
||||||
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
var pageableRequests = new IndexerPageableRequestChain();
|
|
||||||
|
|
||||||
pageableRequests.Add(GetPagedRequests($"{searchCriteria.SanitizedSearchTerm}", searchCriteria.Categories));
|
|
||||||
|
|
||||||
return pageableRequests;
|
|
||||||
}
|
|
||||||
|
|
||||||
private IEnumerable<IndexerRequest> GetPagedRequests(string term, int[] categories, string imdbId = null)
|
|
||||||
{
|
|
||||||
var parameters = new NameValueCollection
|
|
||||||
{
|
|
||||||
{ "action", "torrentstable" },
|
|
||||||
{ "viewtype", "0" },
|
|
||||||
{ "visiblecategories", "Action,Adventure,Animation,Biography,Comedy,Crime,Documentary,Drama,Family,Fantasy,History,Horror,Kids,Music,Mystery,Packs,Romance,Sci-Fi,Short,Sports,Thriller,War,Western" },
|
|
||||||
{ "page", "1" },
|
|
||||||
{ "visibility", "showall" },
|
|
||||||
{ "compression", "showall" },
|
|
||||||
{ "sort", "added" },
|
|
||||||
{ "order", "DESC" },
|
|
||||||
{ "titleonly", "true" },
|
|
||||||
{ "packs", "showall" },
|
|
||||||
{ "bookmarks", "showall" },
|
|
||||||
{ "subscriptions", "showall" },
|
|
||||||
{ "skw", "showall" }
|
|
||||||
};
|
|
||||||
|
|
||||||
if (imdbId.IsNotNullOrWhiteSpace())
|
|
||||||
{
|
|
||||||
parameters.Set("advancedsearchparameters", $"[imdb={imdbId}]");
|
|
||||||
}
|
|
||||||
else if (term.IsNotNullOrWhiteSpace())
|
|
||||||
{
|
|
||||||
parameters.Set("searchstring", term);
|
|
||||||
}
|
|
||||||
|
|
||||||
var queryCats = _capabilities.Categories.MapTorznabCapsToTrackers(categories);
|
|
||||||
if (queryCats.Any())
|
|
||||||
{
|
|
||||||
parameters.Set("hiddenqualities", string.Join(",", queryCats));
|
|
||||||
}
|
|
||||||
|
|
||||||
var searchUrl = _settings.BaseUrl + "torrentsutils.php";
|
|
||||||
|
|
||||||
if (parameters.Count > 0)
|
|
||||||
{
|
|
||||||
searchUrl += $"?{parameters.GetQueryString()}";
|
|
||||||
}
|
|
||||||
|
|
||||||
var request = new IndexerRequest(searchUrl, HttpAccept.Html);
|
|
||||||
|
|
||||||
yield return request;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Func<IDictionary<string, string>> GetCookies { get; set; }
|
|
||||||
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class PirateTheNetParser : IParseIndexerResponse
|
|
||||||
{
|
|
||||||
private readonly UserPassTorrentBaseSettings _settings;
|
|
||||||
private readonly IndexerCapabilitiesCategories _categories;
|
|
||||||
|
|
||||||
public PirateTheNetParser(UserPassTorrentBaseSettings settings, IndexerCapabilitiesCategories categories)
|
|
||||||
{
|
|
||||||
_settings = settings;
|
|
||||||
_categories = categories;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
|
|
||||||
{
|
|
||||||
var releaseInfos = new List<ReleaseInfo>();
|
|
||||||
|
|
||||||
var parser = new HtmlParser();
|
|
||||||
using var dom = parser.ParseDocument(indexerResponse.Content);
|
|
||||||
|
|
||||||
var rows = dom.QuerySelectorAll("table.main > tbody > tr");
|
|
||||||
foreach (var row in rows.Skip(1))
|
|
||||||
{
|
|
||||||
var qDetails = row.QuerySelector("td:nth-of-type(2) > a:nth-of-type(1)");
|
|
||||||
var title = qDetails?.GetAttribute("alt")?.Trim();
|
|
||||||
|
|
||||||
var infoUrl = _settings.BaseUrl + qDetails?.GetAttribute("href")?.TrimStart('/');
|
|
||||||
var downloadUrl = _settings.BaseUrl + row.QuerySelector("td > a:has(img[alt=\"Download Torrent\"])")?.GetAttribute("href")?.TrimStart('/');
|
|
||||||
|
|
||||||
var seeders = ParseUtil.CoerceInt(row.QuerySelector("td:nth-of-type(9)")?.TextContent);
|
|
||||||
var leechers = ParseUtil.CoerceInt(row.QuerySelector("td:nth-of-type(10)")?.TextContent);
|
|
||||||
|
|
||||||
var cat = row.QuerySelector("td:nth-of-type(1) > a > img")?.GetAttribute("src")?.Split('/').Last().Split('.').First() ?? "packs";
|
|
||||||
|
|
||||||
var release = new TorrentInfo
|
|
||||||
{
|
|
||||||
Guid = infoUrl,
|
|
||||||
InfoUrl = infoUrl,
|
|
||||||
DownloadUrl = downloadUrl,
|
|
||||||
Title = title,
|
|
||||||
Categories = _categories.MapTrackerCatToNewznab(cat),
|
|
||||||
Seeders = seeders,
|
|
||||||
Peers = seeders + leechers,
|
|
||||||
Size = ParseUtil.GetBytes(row.QuerySelector("td:nth-of-type(7)")?.TextContent.Trim()),
|
|
||||||
Files = ParseUtil.CoerceInt(row.QuerySelector("td:nth-child(4)")?.TextContent),
|
|
||||||
Grabs = ParseUtil.CoerceInt(row.QuerySelector("td:nth-child(8)")?.TextContent),
|
|
||||||
DownloadVolumeFactor = 0, // ratioless
|
|
||||||
UploadVolumeFactor = 1,
|
|
||||||
MinimumRatio = 1,
|
|
||||||
MinimumSeedTime = 259200, // 72 hours
|
|
||||||
};
|
|
||||||
|
|
||||||
var added = row.QuerySelector("td:nth-of-type(6) > nobr")?.TextContent.Trim();
|
|
||||||
if (added.StartsWith("Today "))
|
|
||||||
{
|
|
||||||
release.PublishDate = DateTime.Now.Date + DateTime.ParseExact(added.Split(" ", 2).Last(), "hh:mm tt", CultureInfo.InvariantCulture).TimeOfDay;
|
|
||||||
}
|
|
||||||
else if (added.StartsWith("Yesterday "))
|
|
||||||
{
|
|
||||||
release.PublishDate = DateTime.Now.AddDays(-1).Date + DateTime.ParseExact(added.Split(" ", 2).Last(), "hh:mm tt", CultureInfo.InvariantCulture).TimeOfDay;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
release.PublishDate = DateTime.ParseExact(added, "MMM d yyyy hh:mm tt", CultureInfo.InvariantCulture);
|
|
||||||
}
|
|
||||||
|
|
||||||
releaseInfos.Add(release);
|
|
||||||
}
|
|
||||||
|
|
||||||
return releaseInfos.ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
|
|
||||||
}
|
|
@ -1,287 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Collections.Specialized;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Net.Http;
|
|
||||||
using System.Text;
|
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System.Web;
|
|
||||||
using AngleSharp.Html.Parser;
|
|
||||||
using NLog;
|
|
||||||
using NzbDrone.Common.Extensions;
|
|
||||||
using NzbDrone.Common.Http;
|
|
||||||
using NzbDrone.Core.Configuration;
|
|
||||||
using NzbDrone.Core.Indexers.Exceptions;
|
|
||||||
using NzbDrone.Core.Indexers.Settings;
|
|
||||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
|
||||||
using NzbDrone.Core.Messaging.Events;
|
|
||||||
using NzbDrone.Core.Parser;
|
|
||||||
using NzbDrone.Core.Parser.Model;
|
|
||||||
|
|
||||||
namespace NzbDrone.Core.Indexers.Definitions
|
|
||||||
{
|
|
||||||
[Obsolete("Remove per Site Request Prowlarr Issue 573")]
|
|
||||||
public class TVVault : TorrentIndexerBase<UserPassTorrentBaseSettings>
|
|
||||||
{
|
|
||||||
public override string Name => "TVVault";
|
|
||||||
public override string[] IndexerUrls => new[] { "https://tv-vault.me/" };
|
|
||||||
private string LoginUrl => Settings.BaseUrl + "login.php";
|
|
||||||
public override string Description => "TV-Vault is a very unique tracker dedicated for old TV shows, TV movies and documentaries.";
|
|
||||||
public override string Language => "en-US";
|
|
||||||
public override Encoding Encoding => Encoding.UTF8;
|
|
||||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
|
|
||||||
public override IndexerCapabilities Capabilities => SetCapabilities();
|
|
||||||
public override TimeSpan RateLimit => TimeSpan.FromSeconds(5);
|
|
||||||
|
|
||||||
public TVVault(IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
|
|
||||||
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public override IIndexerRequestGenerator GetRequestGenerator()
|
|
||||||
{
|
|
||||||
return new TVVaultRequestGenerator { Settings = Settings, Capabilities = Capabilities };
|
|
||||||
}
|
|
||||||
|
|
||||||
public override IParseIndexerResponse GetParser()
|
|
||||||
{
|
|
||||||
return new TVVaultParser(Settings, Capabilities.Categories);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override async Task DoLogin()
|
|
||||||
{
|
|
||||||
var requestBuilder = new HttpRequestBuilder(LoginUrl)
|
|
||||||
{
|
|
||||||
LogResponseContent = true,
|
|
||||||
AllowAutoRedirect = true,
|
|
||||||
Method = HttpMethod.Post
|
|
||||||
};
|
|
||||||
|
|
||||||
var cookies = Cookies;
|
|
||||||
Cookies = null;
|
|
||||||
|
|
||||||
var authLoginRequest = requestBuilder
|
|
||||||
.AddFormParameter("username", Settings.Username)
|
|
||||||
.AddFormParameter("password", Settings.Password)
|
|
||||||
.AddFormParameter("keeplogged", "1")
|
|
||||||
.AddFormParameter("login", "Log+In!")
|
|
||||||
.SetHeader("Content-Type", "application/x-www-form-urlencoded")
|
|
||||||
.SetHeader("Referer", LoginUrl)
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
var response = await ExecuteAuth(authLoginRequest);
|
|
||||||
|
|
||||||
if (CheckIfLoginNeeded(response))
|
|
||||||
{
|
|
||||||
var parser = new HtmlParser();
|
|
||||||
using var dom = parser.ParseDocument(response.Content);
|
|
||||||
var errorMessage = dom.QuerySelector("form#loginform")?.TextContent.Trim();
|
|
||||||
|
|
||||||
throw new IndexerAuthException(errorMessage ?? "Unknown error message, please report.");
|
|
||||||
}
|
|
||||||
|
|
||||||
cookies = response.GetCookies();
|
|
||||||
UpdateCookies(cookies, DateTime.Now.AddDays(30));
|
|
||||||
|
|
||||||
_logger.Debug("TVVault authentication succeeded.");
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override bool CheckIfLoginNeeded(HttpResponse httpResponse)
|
|
||||||
{
|
|
||||||
return !httpResponse.Content.Contains("logout.php");
|
|
||||||
}
|
|
||||||
|
|
||||||
private IndexerCapabilities SetCapabilities()
|
|
||||||
{
|
|
||||||
var caps = new IndexerCapabilities
|
|
||||||
{
|
|
||||||
TvSearchParams = new List<TvSearchParam>
|
|
||||||
{
|
|
||||||
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep, TvSearchParam.ImdbId
|
|
||||||
},
|
|
||||||
MovieSearchParams = new List<MovieSearchParam>
|
|
||||||
{
|
|
||||||
MovieSearchParam.Q, MovieSearchParam.ImdbId
|
|
||||||
},
|
|
||||||
Flags = new List<IndexerFlag>
|
|
||||||
{
|
|
||||||
IndexerFlag.FreeLeech
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.TV);
|
|
||||||
caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.Movies);
|
|
||||||
caps.Categories.AddCategoryMapping(3, NewznabStandardCategory.TVHD);
|
|
||||||
caps.Categories.AddCategoryMapping(4, NewznabStandardCategory.TVSD);
|
|
||||||
|
|
||||||
return caps;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class TVVaultRequestGenerator : IIndexerRequestGenerator
|
|
||||||
{
|
|
||||||
public UserPassTorrentBaseSettings Settings { get; set; }
|
|
||||||
public IndexerCapabilities Capabilities { get; set; }
|
|
||||||
|
|
||||||
private IEnumerable<IndexerRequest> GetPagedRequests(string term, int[] categories, string imdbId = null)
|
|
||||||
{
|
|
||||||
var searchUrl = string.Format("{0}/torrents.php", Settings.BaseUrl.TrimEnd('/'));
|
|
||||||
|
|
||||||
var qc = new NameValueCollection
|
|
||||||
{
|
|
||||||
{ "order_by", "s3" },
|
|
||||||
{ "order_way", "DESC" },
|
|
||||||
{ "disablegrouping", "1" }
|
|
||||||
};
|
|
||||||
|
|
||||||
if (imdbId.IsNotNullOrWhiteSpace())
|
|
||||||
{
|
|
||||||
qc.Add("action", "advanced");
|
|
||||||
qc.Add("imdbid", imdbId);
|
|
||||||
}
|
|
||||||
else if (!string.IsNullOrWhiteSpace(term))
|
|
||||||
{
|
|
||||||
qc.Add("searchstr", StripSearchString(term));
|
|
||||||
}
|
|
||||||
|
|
||||||
var catList = Capabilities.Categories.MapTorznabCapsToTrackers(categories);
|
|
||||||
|
|
||||||
foreach (var cat in catList)
|
|
||||||
{
|
|
||||||
qc.Add($"filter_cat[{cat}]", "1");
|
|
||||||
}
|
|
||||||
|
|
||||||
searchUrl = searchUrl + "?" + qc.GetQueryString();
|
|
||||||
|
|
||||||
var request = new IndexerRequest(searchUrl, HttpAccept.Html);
|
|
||||||
|
|
||||||
yield return request;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
|
|
||||||
{
|
|
||||||
var pageableRequests = new IndexerPageableRequestChain();
|
|
||||||
|
|
||||||
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories, searchCriteria.FullImdbId));
|
|
||||||
|
|
||||||
return pageableRequests;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IndexerPageableRequestChain GetSearchRequests(MusicSearchCriteria searchCriteria)
|
|
||||||
{
|
|
||||||
return new IndexerPageableRequestChain();
|
|
||||||
}
|
|
||||||
|
|
||||||
public IndexerPageableRequestChain GetSearchRequests(TvSearchCriteria searchCriteria)
|
|
||||||
{
|
|
||||||
var pageableRequests = new IndexerPageableRequestChain();
|
|
||||||
|
|
||||||
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedTvSearchString), searchCriteria.Categories, searchCriteria.FullImdbId));
|
|
||||||
|
|
||||||
return pageableRequests;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IndexerPageableRequestChain GetSearchRequests(BookSearchCriteria searchCriteria)
|
|
||||||
{
|
|
||||||
return new IndexerPageableRequestChain();
|
|
||||||
}
|
|
||||||
|
|
||||||
public IndexerPageableRequestChain GetSearchRequests(BasicSearchCriteria searchCriteria)
|
|
||||||
{
|
|
||||||
var pageableRequests = new IndexerPageableRequestChain();
|
|
||||||
|
|
||||||
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories));
|
|
||||||
|
|
||||||
return pageableRequests;
|
|
||||||
}
|
|
||||||
|
|
||||||
private string StripSearchString(string term)
|
|
||||||
{
|
|
||||||
// Search does not support searching with episode numbers so strip it if we have one
|
|
||||||
// AND filter the result later to achieve the proper result
|
|
||||||
term = Regex.Replace(term, @"[S|E]\d\d", string.Empty);
|
|
||||||
return term.Trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Func<IDictionary<string, string>> GetCookies { get; set; }
|
|
||||||
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class TVVaultParser : IParseIndexerResponse
|
|
||||||
{
|
|
||||||
private readonly UserPassTorrentBaseSettings _settings;
|
|
||||||
private readonly IndexerCapabilitiesCategories _categories;
|
|
||||||
|
|
||||||
public TVVaultParser(UserPassTorrentBaseSettings settings, IndexerCapabilitiesCategories categories)
|
|
||||||
{
|
|
||||||
_settings = settings;
|
|
||||||
_categories = categories;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
|
|
||||||
{
|
|
||||||
var torrentInfos = new List<ReleaseInfo>();
|
|
||||||
|
|
||||||
var parser = new HtmlParser();
|
|
||||||
using var doc = parser.ParseDocument(indexerResponse.Content);
|
|
||||||
|
|
||||||
// get params to build download link (user could be banned without those params)
|
|
||||||
var rssFeedUri = new Uri(_settings.BaseUrl + doc.QuerySelector("link[href^=\"/feeds.php?feed=\"]")
|
|
||||||
.GetAttribute("href"));
|
|
||||||
var rssFeedQuery = HttpUtility.ParseQueryString(rssFeedUri.Query);
|
|
||||||
var downloadLinkExtraParams = "&authkey=" + rssFeedQuery["authkey"] + "&torrent_pass=" + rssFeedQuery["passkey"];
|
|
||||||
|
|
||||||
var rows = doc.QuerySelectorAll("table.torrent_table > tbody > tr.torrent");
|
|
||||||
|
|
||||||
foreach (var row in rows)
|
|
||||||
{
|
|
||||||
var qDetailsLink = row.QuerySelector("a[href^=\"torrents.php?id=\"]");
|
|
||||||
var title = qDetailsLink.TextContent;
|
|
||||||
|
|
||||||
var description = qDetailsLink.NextSibling.TextContent.Trim();
|
|
||||||
title += " " + description;
|
|
||||||
var details = _settings.BaseUrl + qDetailsLink.GetAttribute("href");
|
|
||||||
var torrentId = qDetailsLink.GetAttribute("href").Split('=').Last();
|
|
||||||
var link = _settings.BaseUrl + "torrents.php?action=download&id=" + torrentId + downloadLinkExtraParams;
|
|
||||||
|
|
||||||
var files = ParseUtil.CoerceInt(row.QuerySelector("td:nth-child(3)").TextContent);
|
|
||||||
var publishDate = DateTimeUtil.FromTimeAgo(row.QuerySelector("td:nth-child(4)").TextContent);
|
|
||||||
var size = ParseUtil.GetBytes(row.QuerySelector("td:nth-child(5)").FirstChild.TextContent);
|
|
||||||
var grabs = ParseUtil.CoerceInt(row.QuerySelector("td:nth-child(6)").TextContent);
|
|
||||||
var seeders = ParseUtil.CoerceInt(row.QuerySelector("td:nth-child(7)").TextContent);
|
|
||||||
var leechers = ParseUtil.CoerceInt(row.QuerySelector("td:nth-child(8)").TextContent);
|
|
||||||
|
|
||||||
var dlVolumeFactor = row.QuerySelector("strong.freeleech_normal") != null ? 0 : 1;
|
|
||||||
|
|
||||||
var category = new List<IndexerCategory> { TvCategoryFromQualityParser.ParseTvShowQuality(description) };
|
|
||||||
|
|
||||||
var release = new TorrentInfo
|
|
||||||
{
|
|
||||||
MinimumRatio = 1,
|
|
||||||
MinimumSeedTime = 0,
|
|
||||||
Description = description,
|
|
||||||
Title = title,
|
|
||||||
PublishDate = publishDate,
|
|
||||||
Categories = category,
|
|
||||||
DownloadUrl = link,
|
|
||||||
InfoUrl = details,
|
|
||||||
Guid = link,
|
|
||||||
Seeders = seeders,
|
|
||||||
Peers = leechers + seeders,
|
|
||||||
Size = size,
|
|
||||||
Grabs = grabs,
|
|
||||||
Files = files,
|
|
||||||
DownloadVolumeFactor = dlVolumeFactor,
|
|
||||||
UploadVolumeFactor = 1
|
|
||||||
};
|
|
||||||
|
|
||||||
torrentInfos.Add(release);
|
|
||||||
}
|
|
||||||
|
|
||||||
return torrentInfos.ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in new issue