parent
c923982711
commit
76bfd29f23
@ -0,0 +1,312 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using AngleSharp.Html.Parser;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Annotations;
|
||||
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;
|
||||
|
||||
public class Uniotaku : TorrentIndexerBase<UniotakuSettings>
|
||||
{
|
||||
public override string Name => "UniOtaku";
|
||||
public override string[] IndexerUrls => new[] { "https://tracker.uniotaku.com/" };
|
||||
public override string Description => "UniOtaku is a BRAZILIAN Semi-Private Torrent Tracker for ANIME";
|
||||
public override string Language => "pt-BR";
|
||||
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.SemiPrivate;
|
||||
public override IndexerCapabilities Capabilities => SetCapabilities();
|
||||
|
||||
public Uniotaku(IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
|
||||
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
|
||||
{
|
||||
}
|
||||
|
||||
public override IIndexerRequestGenerator GetRequestGenerator()
|
||||
{
|
||||
return new UniotakuRequestGenerator(Settings, Capabilities);
|
||||
}
|
||||
|
||||
public override IParseIndexerResponse GetParser()
|
||||
{
|
||||
return new UniotakuParser(Settings, Capabilities.Categories);
|
||||
}
|
||||
|
||||
protected override async Task DoLogin()
|
||||
{
|
||||
var loginUrl = Settings.BaseUrl + "account-login.php";
|
||||
var requestBuilder = new HttpRequestBuilder(loginUrl);
|
||||
|
||||
var cookies = Cookies;
|
||||
Cookies = null;
|
||||
|
||||
var authLoginRequest = requestBuilder
|
||||
.Post()
|
||||
.AddFormParameter("username", Settings.Username)
|
||||
.AddFormParameter("password", Settings.Password)
|
||||
.AddFormParameter("manter", "1")
|
||||
.SetHeader("Content-Type", "application/x-www-form-urlencoded")
|
||||
.SetHeader("Referer", Settings.BaseUrl)
|
||||
.Build();
|
||||
|
||||
var response = await ExecuteAuth(authLoginRequest);
|
||||
|
||||
if (CheckIfLoginNeeded(response))
|
||||
{
|
||||
var parser = new HtmlParser();
|
||||
var dom = parser.ParseDocument(response.Content);
|
||||
var errorMessage = dom.QuerySelector(".login-content span.text-red")?.TextContent.Trim();
|
||||
|
||||
throw new IndexerAuthException(errorMessage ?? "Unknown error message, please report.");
|
||||
}
|
||||
|
||||
cookies = response.GetCookies();
|
||||
UpdateCookies(cookies, DateTime.Now.AddDays(30));
|
||||
|
||||
_logger.Debug("Authentication succeeded");
|
||||
}
|
||||
|
||||
protected override bool CheckIfLoginNeeded(HttpResponse httpResponse)
|
||||
{
|
||||
return !httpResponse.GetCookies().ContainsKey("uid") || !httpResponse.GetCookies().ContainsKey("pass");
|
||||
}
|
||||
|
||||
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();
|
||||
var dom = parser.ParseDocument(response.Content);
|
||||
var downloadLink = dom.QuerySelector("a[href^=\"download.php?id=\"]")?.GetAttribute("href")?.Trim();
|
||||
|
||||
if (downloadLink == null)
|
||||
{
|
||||
throw new Exception($"Failed to fetch download link from {link}");
|
||||
}
|
||||
|
||||
return await base.Download(new Uri(Settings.BaseUrl + downloadLink));
|
||||
}
|
||||
|
||||
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(28, NewznabStandardCategory.TVAnime, "Anime");
|
||||
caps.Categories.AddCategoryMapping(47, NewznabStandardCategory.MoviesOther, "Filme");
|
||||
caps.Categories.AddCategoryMapping(48, NewznabStandardCategory.TVAnime, "OVA");
|
||||
caps.Categories.AddCategoryMapping(49, NewznabStandardCategory.BooksComics, "Mangá");
|
||||
caps.Categories.AddCategoryMapping(50, NewznabStandardCategory.TVOther, "Dorama");
|
||||
caps.Categories.AddCategoryMapping(51, NewznabStandardCategory.Audio, "OST");
|
||||
caps.Categories.AddCategoryMapping(52, NewznabStandardCategory.TVAnime, "Anime Completo");
|
||||
caps.Categories.AddCategoryMapping(53, NewznabStandardCategory.BooksComics, "Mangá Completo");
|
||||
caps.Categories.AddCategoryMapping(54, NewznabStandardCategory.TVOther, "Dorama Completo");
|
||||
caps.Categories.AddCategoryMapping(55, NewznabStandardCategory.XXX, "Hentai");
|
||||
caps.Categories.AddCategoryMapping(56, NewznabStandardCategory.XXXOther, "H Doujinshi");
|
||||
caps.Categories.AddCategoryMapping(57, NewznabStandardCategory.TVOther, "Tokusatsu");
|
||||
|
||||
return caps;
|
||||
}
|
||||
}
|
||||
|
||||
public class UniotakuRequestGenerator : IIndexerRequestGenerator
|
||||
{
|
||||
private readonly UniotakuSettings _settings;
|
||||
private readonly IndexerCapabilities _capabilities;
|
||||
|
||||
public UniotakuRequestGenerator(UniotakuSettings settings, IndexerCapabilities capabilities)
|
||||
{
|
||||
_settings = settings;
|
||||
_capabilities = capabilities;
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
pageableRequests.Add(GetPagedRequests($"{searchCriteria.SanitizedSearchTerm}", searchCriteria.Categories));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(MusicSearchCriteria searchCriteria)
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
pageableRequests.Add(GetPagedRequests($"{searchCriteria.SanitizedSearchTerm}", searchCriteria.Categories));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(TvSearchCriteria searchCriteria)
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
pageableRequests.Add(GetPagedRequests($"{searchCriteria.SanitizedSearchTerm}", searchCriteria.Categories));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(BookSearchCriteria searchCriteria)
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
pageableRequests.Add(GetPagedRequests($"{searchCriteria.SanitizedSearchTerm}", searchCriteria.Categories));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(term))
|
||||
{
|
||||
term = "%" + Regex.Replace(term, @"[ -._]+", "%").Trim() + "%";
|
||||
}
|
||||
|
||||
var categoryMapping = _capabilities.Categories.MapTorznabCapsToTrackers(categories);
|
||||
|
||||
var parameters = new NameValueCollection
|
||||
{
|
||||
{ "categoria", categoryMapping.FirstIfSingleOrDefault("0") },
|
||||
{ "grupo", "0" },
|
||||
{ "status", _settings.FreeleechOnly ? "1" : "0" },
|
||||
{ "ordenar", "0" },
|
||||
{ "start", "0" },
|
||||
{ "length", "100" },
|
||||
{ "search[value]", term ?? string.Empty },
|
||||
{ "search[regex]", "false" },
|
||||
};
|
||||
|
||||
var searchUrl = $"{_settings.BaseUrl}torrents_.php?{parameters.GetQueryString()}";
|
||||
|
||||
yield return new IndexerRequest(searchUrl, HttpAccept.Html);
|
||||
}
|
||||
|
||||
public Func<IDictionary<string, string>> GetCookies { get; set; }
|
||||
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
|
||||
}
|
||||
|
||||
public class UniotakuParser : IParseIndexerResponse
|
||||
{
|
||||
private readonly UniotakuSettings _settings;
|
||||
private readonly IndexerCapabilitiesCategories _categories;
|
||||
|
||||
public UniotakuParser(UniotakuSettings settings, IndexerCapabilitiesCategories categories)
|
||||
{
|
||||
_settings = settings;
|
||||
_categories = categories;
|
||||
}
|
||||
|
||||
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
|
||||
{
|
||||
var releaseInfos = new List<ReleaseInfo>();
|
||||
|
||||
var parser = new HtmlParser();
|
||||
|
||||
var jsonContent = JObject.Parse(indexerResponse.Content);
|
||||
|
||||
var publishDate = DateTime.Now;
|
||||
foreach (var item in jsonContent.Value<JArray>("data"))
|
||||
{
|
||||
var detailsDom = parser.ParseDocument(item.SelectToken("[0]").Value<string>());
|
||||
var categoryDom = parser.ParseDocument(item.SelectToken("[1]").Value<string>());
|
||||
var groupDom = parser.ParseDocument(item.SelectToken("[7]").Value<string>());
|
||||
|
||||
var qTitleLink = detailsDom.QuerySelector("a[href^=\"torrents-details.php?id=\"]");
|
||||
var title = qTitleLink?.TextContent.Trim();
|
||||
var infoUrl = _settings.BaseUrl + qTitleLink?.GetAttribute("href");
|
||||
|
||||
var category = categoryDom.QuerySelector("img[alt]")?.GetAttribute("alt")?.Trim() ?? "Anime";
|
||||
|
||||
var releaseGroup = groupDom.QuerySelector("a[href*=\"teams-view.php?id=\"]")?.TextContent.Trim();
|
||||
if (!string.IsNullOrWhiteSpace(releaseGroup))
|
||||
{
|
||||
title += $" [{releaseGroup}]";
|
||||
}
|
||||
|
||||
var seeders = item.SelectToken("[3]")?.Value<int>();
|
||||
var leechers = item.SelectToken("[4]")?.Value<int>();
|
||||
|
||||
publishDate = publishDate.AddMinutes(-1);
|
||||
|
||||
var release = new TorrentInfo
|
||||
{
|
||||
Guid = infoUrl,
|
||||
InfoUrl = infoUrl,
|
||||
DownloadUrl = infoUrl,
|
||||
Title = title,
|
||||
Categories = _categories.MapTrackerCatDescToNewznab(category),
|
||||
Size = ParseUtil.GetBytes(item.SelectToken("[6]")?.Value<string>()),
|
||||
Grabs = item.SelectToken("[5]")?.Value<int>(),
|
||||
Seeders = seeders,
|
||||
Peers = seeders + leechers,
|
||||
PublishDate = publishDate,
|
||||
DownloadVolumeFactor =
|
||||
detailsDom.QuerySelector("img[src*=\"images/free.gif\"]") != null ? 0 :
|
||||
detailsDom.QuerySelector("img[src*=\"images/silverdownload.gif\"]") != null ? 0.5 : 1,
|
||||
UploadVolumeFactor = 1,
|
||||
MinimumRatio = 0.7
|
||||
};
|
||||
|
||||
releaseInfos.Add(release);
|
||||
}
|
||||
|
||||
return releaseInfos;
|
||||
}
|
||||
|
||||
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
|
||||
}
|
||||
|
||||
public class UniotakuSettings : UserPassTorrentBaseSettings
|
||||
{
|
||||
public UniotakuSettings()
|
||||
{
|
||||
FreeleechOnly = false;
|
||||
}
|
||||
|
||||
[FieldDefinition(4, Label = "Freeleech Only", Type = FieldType.Checkbox, HelpText = "Search Freeleech torrents only")]
|
||||
public bool FreeleechOnly { get; set; }
|
||||
}
|
Loading…
Reference in new issue