|
|
|
@ -29,19 +29,23 @@ namespace NzbDrone.Core.Indexers.Definitions
|
|
|
|
|
public override IndexerCapabilities Capabilities => SetCapabilities();
|
|
|
|
|
private string LoginUrl => Settings.BaseUrl + "takelogin.php";
|
|
|
|
|
|
|
|
|
|
public ImmortalSeed(IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
|
|
|
|
|
public ImmortalSeed(IIndexerHttpClient httpClient,
|
|
|
|
|
IEventAggregator eventAggregator,
|
|
|
|
|
IIndexerStatusService indexerStatusService,
|
|
|
|
|
IConfigService configService,
|
|
|
|
|
Logger logger)
|
|
|
|
|
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override IIndexerRequestGenerator GetRequestGenerator()
|
|
|
|
|
{
|
|
|
|
|
return new ImmortalSeedRequestGenerator { Settings = Settings, Capabilities = Capabilities };
|
|
|
|
|
return new ImmortalSeedRequestGenerator(Settings, Capabilities);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override IParseIndexerResponse GetParser()
|
|
|
|
|
{
|
|
|
|
|
return new ImmortalSeedParser(Settings, Capabilities.Categories);
|
|
|
|
|
return new ImmortalSeedParser(Capabilities.Categories);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected override async Task DoLogin()
|
|
|
|
@ -49,15 +53,11 @@ namespace NzbDrone.Core.Indexers.Definitions
|
|
|
|
|
var requestBuilder = new HttpRequestBuilder(LoginUrl)
|
|
|
|
|
{
|
|
|
|
|
LogResponseContent = true,
|
|
|
|
|
AllowAutoRedirect = true
|
|
|
|
|
AllowAutoRedirect = true,
|
|
|
|
|
Method = HttpMethod.Post
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
requestBuilder.Method = HttpMethod.Post;
|
|
|
|
|
requestBuilder.PostProcess += r => r.RequestTimeout = TimeSpan.FromSeconds(15);
|
|
|
|
|
|
|
|
|
|
var cookies = Cookies;
|
|
|
|
|
|
|
|
|
|
Cookies = null;
|
|
|
|
|
var authLoginRequest = requestBuilder
|
|
|
|
|
.AddFormParameter("username", Settings.Username)
|
|
|
|
|
.AddFormParameter("password", Settings.Password)
|
|
|
|
@ -71,7 +71,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
|
|
|
|
throw new IndexerAuthException("ImmortalSeed Auth Failed");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cookies = response.GetCookies();
|
|
|
|
|
var cookies = response.GetCookies();
|
|
|
|
|
UpdateCookies(cookies, DateTime.Now + TimeSpan.FromDays(30));
|
|
|
|
|
|
|
|
|
|
_logger.Debug("ImmortalSeed authentication succeeded.");
|
|
|
|
@ -156,47 +156,20 @@ namespace NzbDrone.Core.Indexers.Definitions
|
|
|
|
|
|
|
|
|
|
public class ImmortalSeedRequestGenerator : IIndexerRequestGenerator
|
|
|
|
|
{
|
|
|
|
|
public UserPassTorrentBaseSettings Settings { get; set; }
|
|
|
|
|
public IndexerCapabilities Capabilities { get; set; }
|
|
|
|
|
|
|
|
|
|
private IEnumerable<IndexerRequest> GetPagedRequests(SearchCriteriaBase searchCriteria)
|
|
|
|
|
{
|
|
|
|
|
var parameters = new NameValueCollection();
|
|
|
|
|
|
|
|
|
|
var term = searchCriteria.SanitizedSearchTerm;
|
|
|
|
|
|
|
|
|
|
if (term.IsNotNullOrWhiteSpace())
|
|
|
|
|
{
|
|
|
|
|
parameters.Add("do", "search");
|
|
|
|
|
parameters.Add("keywords", term.Trim());
|
|
|
|
|
parameters.Add("search_type", "t_name");
|
|
|
|
|
parameters.Add("category", "0");
|
|
|
|
|
parameters.Add("include_dead_torrents", "no");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var queryCats = Capabilities.Categories.MapTorznabCapsToTrackers(searchCriteria.Categories);
|
|
|
|
|
|
|
|
|
|
if (queryCats.Count > 0)
|
|
|
|
|
{
|
|
|
|
|
parameters.Add("selectedcats2", string.Join(",", queryCats));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var searchUrl = Settings.BaseUrl + "browse.php";
|
|
|
|
|
private readonly UserPassTorrentBaseSettings _settings;
|
|
|
|
|
private readonly IndexerCapabilities _capabilities;
|
|
|
|
|
|
|
|
|
|
if (parameters.Count > 0)
|
|
|
|
|
public ImmortalSeedRequestGenerator(UserPassTorrentBaseSettings settings, IndexerCapabilities capabilities)
|
|
|
|
|
{
|
|
|
|
|
searchUrl += $"?{parameters.GetQueryString()}";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var request = new IndexerRequest(searchUrl, HttpAccept.Html);
|
|
|
|
|
|
|
|
|
|
yield return request;
|
|
|
|
|
_settings = settings;
|
|
|
|
|
_capabilities = capabilities;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
|
|
|
|
|
{
|
|
|
|
|
var pageableRequests = new IndexerPageableRequestChain();
|
|
|
|
|
pageableRequests.Add(GetPagedRequests(searchCriteria));
|
|
|
|
|
|
|
|
|
|
pageableRequests.Add(GetPagedRequests($"{searchCriteria.SanitizedSearchTerm}", searchCriteria.Categories));
|
|
|
|
|
|
|
|
|
|
return pageableRequests;
|
|
|
|
|
}
|
|
|
|
@ -204,7 +177,8 @@ namespace NzbDrone.Core.Indexers.Definitions
|
|
|
|
|
public IndexerPageableRequestChain GetSearchRequests(MusicSearchCriteria searchCriteria)
|
|
|
|
|
{
|
|
|
|
|
var pageableRequests = new IndexerPageableRequestChain();
|
|
|
|
|
pageableRequests.Add(GetPagedRequests(searchCriteria));
|
|
|
|
|
|
|
|
|
|
pageableRequests.Add(GetPagedRequests($"{searchCriteria.SanitizedSearchTerm}", searchCriteria.Categories));
|
|
|
|
|
|
|
|
|
|
return pageableRequests;
|
|
|
|
|
}
|
|
|
|
@ -212,7 +186,8 @@ namespace NzbDrone.Core.Indexers.Definitions
|
|
|
|
|
public IndexerPageableRequestChain GetSearchRequests(TvSearchCriteria searchCriteria)
|
|
|
|
|
{
|
|
|
|
|
var pageableRequests = new IndexerPageableRequestChain();
|
|
|
|
|
pageableRequests.Add(GetPagedRequests(searchCriteria));
|
|
|
|
|
|
|
|
|
|
pageableRequests.Add(GetPagedRequests($"{searchCriteria.SanitizedTvSearchString}", searchCriteria.Categories));
|
|
|
|
|
|
|
|
|
|
return pageableRequests;
|
|
|
|
|
}
|
|
|
|
@ -220,7 +195,8 @@ namespace NzbDrone.Core.Indexers.Definitions
|
|
|
|
|
public IndexerPageableRequestChain GetSearchRequests(BookSearchCriteria searchCriteria)
|
|
|
|
|
{
|
|
|
|
|
var pageableRequests = new IndexerPageableRequestChain();
|
|
|
|
|
pageableRequests.Add(GetPagedRequests(searchCriteria));
|
|
|
|
|
|
|
|
|
|
pageableRequests.Add(GetPagedRequests($"{searchCriteria.SanitizedSearchTerm}", searchCriteria.Categories));
|
|
|
|
|
|
|
|
|
|
return pageableRequests;
|
|
|
|
|
}
|
|
|
|
@ -228,76 +204,104 @@ namespace NzbDrone.Core.Indexers.Definitions
|
|
|
|
|
public IndexerPageableRequestChain GetSearchRequests(BasicSearchCriteria searchCriteria)
|
|
|
|
|
{
|
|
|
|
|
var pageableRequests = new IndexerPageableRequestChain();
|
|
|
|
|
pageableRequests.Add(GetPagedRequests(searchCriteria));
|
|
|
|
|
|
|
|
|
|
pageableRequests.Add(GetPagedRequests($"{searchCriteria.SanitizedSearchTerm}", searchCriteria.Categories));
|
|
|
|
|
|
|
|
|
|
return pageableRequests;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private IEnumerable<IndexerRequest> GetPagedRequests(string term, int[] categories)
|
|
|
|
|
{
|
|
|
|
|
var parameters = new NameValueCollection();
|
|
|
|
|
|
|
|
|
|
if (term.IsNotNullOrWhiteSpace())
|
|
|
|
|
{
|
|
|
|
|
parameters.Add("do", "search");
|
|
|
|
|
parameters.Add("keywords", term.Trim());
|
|
|
|
|
parameters.Add("search_type", "t_name");
|
|
|
|
|
parameters.Add("category", "0");
|
|
|
|
|
parameters.Add("include_dead_torrents", "no");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var queryCats = _capabilities.Categories.MapTorznabCapsToTrackers(categories);
|
|
|
|
|
|
|
|
|
|
if (queryCats.Any())
|
|
|
|
|
{
|
|
|
|
|
parameters.Add("selectedcats2", string.Join(",", queryCats));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var searchUrl = _settings.BaseUrl + "browse.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 ImmortalSeedParser : IParseIndexerResponse
|
|
|
|
|
{
|
|
|
|
|
private readonly UserPassTorrentBaseSettings _settings;
|
|
|
|
|
private readonly IndexerCapabilitiesCategories _categories;
|
|
|
|
|
|
|
|
|
|
public ImmortalSeedParser(UserPassTorrentBaseSettings settings, IndexerCapabilitiesCategories categories)
|
|
|
|
|
public ImmortalSeedParser(IndexerCapabilitiesCategories categories)
|
|
|
|
|
{
|
|
|
|
|
_settings = settings;
|
|
|
|
|
_categories = categories;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
|
|
|
|
|
{
|
|
|
|
|
var torrentInfos = new List<TorrentInfo>();
|
|
|
|
|
var releaseInfos = new List<ReleaseInfo>();
|
|
|
|
|
|
|
|
|
|
var parser = new HtmlParser();
|
|
|
|
|
var dom = parser.ParseDocument(indexerResponse.Content);
|
|
|
|
|
|
|
|
|
|
var rows = dom.QuerySelectorAll("#sortabletable tr:has(a[href*=\"details.php?id=\"])");
|
|
|
|
|
var rows = dom.QuerySelectorAll("table#sortabletable > tbody > tr:has(a[href*=\"details.php?id=\"])");
|
|
|
|
|
foreach (var row in rows)
|
|
|
|
|
{
|
|
|
|
|
var release = new TorrentInfo();
|
|
|
|
|
|
|
|
|
|
var qDetails = row.QuerySelector("div > a[href*=\"details.php?id=\"]"); // details link, release name get's shortened if it's to long
|
|
|
|
|
// details link, release name gets shortened if it's to long
|
|
|
|
|
var qDetails = row.QuerySelector("div > a[href*=\"details.php?id=\"]");
|
|
|
|
|
|
|
|
|
|
// use Title from tooltip or fallback to Details link if there's no tooltip
|
|
|
|
|
var qTitle = row.QuerySelector(".tooltip-content > div:nth-of-type(1)") ?? qDetails;
|
|
|
|
|
release.Title = qTitle.TextContent;
|
|
|
|
|
|
|
|
|
|
var qDesciption = row.QuerySelectorAll(".tooltip-content > div");
|
|
|
|
|
if (qDesciption.Any())
|
|
|
|
|
{
|
|
|
|
|
release.Description = qDesciption[1].TextContent.Trim();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var qLink = row.QuerySelector("a[href*=\"download.php\"]");
|
|
|
|
|
release.DownloadUrl = qLink.GetAttribute("href");
|
|
|
|
|
release.Guid = release.DownloadUrl;
|
|
|
|
|
release.InfoUrl = qDetails.GetAttribute("href");
|
|
|
|
|
|
|
|
|
|
// 2021-03-17 03:39 AM
|
|
|
|
|
var dateString = row.QuerySelectorAll("td:nth-of-type(2) div").Last().LastChild.TextContent.Trim();
|
|
|
|
|
release.PublishDate = DateTime.ParseExact(dateString, "yyyy-MM-dd hh:mm tt", CultureInfo.InvariantCulture);
|
|
|
|
|
|
|
|
|
|
var sizeStr = row.QuerySelector("td:nth-of-type(5)").TextContent.Trim();
|
|
|
|
|
release.Size = ParseUtil.GetBytes(sizeStr);
|
|
|
|
|
var title = qTitle?.TextContent.Trim();
|
|
|
|
|
var description = row.QuerySelector(".tooltip-content > div:nth-of-type(2)")?.TextContent.Replace("|", ",").Replace(" ", "").Trim();
|
|
|
|
|
|
|
|
|
|
release.Seeders = ParseUtil.CoerceInt(row.QuerySelector("td:nth-of-type(7)").TextContent.Trim());
|
|
|
|
|
release.Peers = ParseUtil.CoerceInt(row.QuerySelector("td:nth-of-type(8)").TextContent.Trim()) + release.Seeders;
|
|
|
|
|
var infoUrl = qDetails?.GetAttribute("href");
|
|
|
|
|
var downloadUrl = row.QuerySelector("a[href*=\"download.php\"]")?.GetAttribute("href");
|
|
|
|
|
|
|
|
|
|
var catLink = row.QuerySelector("td:nth-of-type(1) a").GetAttribute("href");
|
|
|
|
|
var catSplit = catLink.IndexOf("category=");
|
|
|
|
|
if (catSplit > -1)
|
|
|
|
|
{
|
|
|
|
|
catLink = catLink.Substring(catSplit + 9);
|
|
|
|
|
}
|
|
|
|
|
var seeders = ParseUtil.CoerceInt(row.QuerySelector("td:nth-of-type(7)")?.TextContent);
|
|
|
|
|
var peers = seeders + ParseUtil.CoerceInt(row.QuerySelector("td:nth-of-type(8)")?.TextContent.Trim());
|
|
|
|
|
|
|
|
|
|
release.Categories = _categories.MapTrackerCatToNewznab(catLink);
|
|
|
|
|
var categoryLink = row.QuerySelector("td:nth-of-type(1) a").GetAttribute("href");
|
|
|
|
|
var cat = ParseUtil.GetArgumentFromQueryString(categoryLink, "category");
|
|
|
|
|
|
|
|
|
|
var grabs = row.QuerySelector("td:nth-child(6)").TextContent;
|
|
|
|
|
release.Grabs = ParseUtil.CoerceInt(grabs);
|
|
|
|
|
// 2021-03-17 03:39 AM
|
|
|
|
|
var added = row.QuerySelector("td:nth-of-type(2) > div:last-child").LastChild.TextContent.Trim();
|
|
|
|
|
|
|
|
|
|
var release = new TorrentInfo
|
|
|
|
|
{
|
|
|
|
|
Guid = infoUrl,
|
|
|
|
|
InfoUrl = infoUrl,
|
|
|
|
|
DownloadUrl = downloadUrl,
|
|
|
|
|
Title = title,
|
|
|
|
|
Description = description,
|
|
|
|
|
Categories = _categories.MapTrackerCatToNewznab(cat),
|
|
|
|
|
Seeders = seeders,
|
|
|
|
|
Peers = peers,
|
|
|
|
|
Size = ParseUtil.GetBytes(row.QuerySelector("td:nth-of-type(5)")?.TextContent.Trim()),
|
|
|
|
|
Grabs = ParseUtil.CoerceInt(row.QuerySelector("td:nth-child(6)")?.TextContent),
|
|
|
|
|
PublishDate = DateTime.ParseExact(added, "yyyy-MM-dd hh:mm tt", CultureInfo.InvariantCulture),
|
|
|
|
|
UploadVolumeFactor = row.QuerySelector("img[title^=\"x2 Torrent\"]") != null ? 2 : 1,
|
|
|
|
|
MinimumRatio = 1,
|
|
|
|
|
MinimumSeedTime = 86400 // 24 hours
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (row.QuerySelector("img[title^=\"Free Torrent\"]") != null)
|
|
|
|
|
{
|
|
|
|
@ -312,19 +316,10 @@ namespace NzbDrone.Core.Indexers.Definitions
|
|
|
|
|
release.DownloadVolumeFactor = 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (row.QuerySelector("img[title^=\"x2 Torrent\"]") != null)
|
|
|
|
|
{
|
|
|
|
|
release.UploadVolumeFactor = 2;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
release.UploadVolumeFactor = 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
torrentInfos.Add(release);
|
|
|
|
|
releaseInfos.Add(release);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return torrentInfos.ToArray();
|
|
|
|
|
return releaseInfos.ToArray();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
|
|
|
|
|