From 580113d6cef49bbde7ac0c4780d12d05d314ef1a Mon Sep 17 00:00:00 2001 From: Qstick Date: Wed, 28 Jul 2021 19:37:51 -0400 Subject: [PATCH] New: (Indexer) TorrentSyndikat --- .../Indexers/Definitions/TorrentSyndikat.cs | 373 ++++++++++++++++++ 1 file changed, 373 insertions(+) create mode 100644 src/NzbDrone.Core/Indexers/Definitions/TorrentSyndikat.cs diff --git a/src/NzbDrone.Core/Indexers/Definitions/TorrentSyndikat.cs b/src/NzbDrone.Core/Indexers/Definitions/TorrentSyndikat.cs new file mode 100644 index 000000000..97cd08897 --- /dev/null +++ b/src/NzbDrone.Core/Indexers/Definitions/TorrentSyndikat.cs @@ -0,0 +1,373 @@ +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using FluentValidation; +using Newtonsoft.Json.Linq; +using NLog; +using NzbDrone.Common.Http; +using NzbDrone.Core.Annotations; +using NzbDrone.Core.Configuration; +using NzbDrone.Core.IndexerSearch.Definitions; +using NzbDrone.Core.Messaging.Events; +using NzbDrone.Core.Parser; +using NzbDrone.Core.Parser.Model; +using NzbDrone.Core.Validation; + +namespace NzbDrone.Core.Indexers.Definitions +{ + public class TorrentSyndikat : TorrentIndexerBase + { + public override string Name => "TorrentSyndikat"; + public override string[] IndexerUrls => new[] { "https://torrent-syndikat.org/" }; + public override string Description => "A German general tracker"; + public override string Language => "de-de"; + public override Encoding Encoding => Encoding.UTF8; + public override DownloadProtocol Protocol => DownloadProtocol.Torrent; + public override IndexerPrivacy Privacy => IndexerPrivacy.Private; + public override IndexerCapabilities Capabilities => SetCapabilities(); + + public TorrentSyndikat(IHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger) + : base(httpClient, eventAggregator, indexerStatusService, configService, logger) + { + } + + public override IIndexerRequestGenerator GetRequestGenerator() + { + return new TorrentSyndikatRequestGenerator() { Settings = Settings, Capabilities = Capabilities }; + } + + public override IParseIndexerResponse GetParser() + { + return new TorrentSyndikatParser(Settings, Capabilities.Categories); + } + + private IndexerCapabilities SetCapabilities() + { + var caps = new IndexerCapabilities + { + TvSearchParams = new List + { + TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep + }, + MovieSearchParams = new List + { + MovieSearchParam.Q, MovieSearchParam.ImdbId + }, + MusicSearchParams = new List + { + MusicSearchParam.Q + }, + BookSearchParams = new List + { + BookSearchParam.Q + } + }; + + caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.PC, "Apps / Windows"); + caps.Categories.AddCategoryMapping(13, NewznabStandardCategory.PC, "Apps / Linux"); + caps.Categories.AddCategoryMapping(4, NewznabStandardCategory.PCMac, "Apps / MacOS"); + caps.Categories.AddCategoryMapping(6, NewznabStandardCategory.PC, "Apps / Misc"); + + caps.Categories.AddCategoryMapping(50, NewznabStandardCategory.PCGames, "Spiele / Windows"); + caps.Categories.AddCategoryMapping(51, NewznabStandardCategory.PCGames, "Spiele / MacOS"); + caps.Categories.AddCategoryMapping(52, NewznabStandardCategory.PCGames, "Spiele / Linux"); + caps.Categories.AddCategoryMapping(8, NewznabStandardCategory.ConsoleOther, "Spiele / Playstation"); + caps.Categories.AddCategoryMapping(7, NewznabStandardCategory.ConsoleOther, "Spiele / Nintendo"); + caps.Categories.AddCategoryMapping(32, NewznabStandardCategory.ConsoleOther, "Spiele / XBOX"); + + caps.Categories.AddCategoryMapping(42, NewznabStandardCategory.MoviesUHD, "Filme / 2160p"); + caps.Categories.AddCategoryMapping(9, NewznabStandardCategory.MoviesHD, "Filme / 1080p"); + caps.Categories.AddCategoryMapping(20, NewznabStandardCategory.MoviesHD, "Filme / 720p"); + caps.Categories.AddCategoryMapping(10, NewznabStandardCategory.MoviesSD, "Filme / SD"); + + caps.Categories.AddCategoryMapping(43, NewznabStandardCategory.TVUHD, "Serien / 2160p"); + caps.Categories.AddCategoryMapping(53, NewznabStandardCategory.TVHD, "Serien / 1080p"); + caps.Categories.AddCategoryMapping(54, NewznabStandardCategory.TVHD, "Serien / 720p"); + caps.Categories.AddCategoryMapping(15, NewznabStandardCategory.TVSD, "Serien / SD"); + caps.Categories.AddCategoryMapping(30, NewznabStandardCategory.TVSport, "Serien / Sport"); + + caps.Categories.AddCategoryMapping(44, NewznabStandardCategory.TVUHD, "Serienpacks / 2160p"); + caps.Categories.AddCategoryMapping(55, NewznabStandardCategory.TVHD, "Serienpacks / 1080p"); + caps.Categories.AddCategoryMapping(56, NewznabStandardCategory.TVHD, "Serienpacks / 720p"); + caps.Categories.AddCategoryMapping(27, NewznabStandardCategory.TVSD, "Serienpacks / SD"); + + caps.Categories.AddCategoryMapping(24, NewznabStandardCategory.AudioLossless, "Audio / Musik / FLAC"); + caps.Categories.AddCategoryMapping(25, NewznabStandardCategory.AudioMP3, "Audio / Musik / MP3"); + caps.Categories.AddCategoryMapping(35, NewznabStandardCategory.AudioOther, "Audio / Other"); + caps.Categories.AddCategoryMapping(18, NewznabStandardCategory.AudioAudiobook, "Audio / aBooks"); + caps.Categories.AddCategoryMapping(33, NewznabStandardCategory.AudioVideo, "Audio / Videos"); + + caps.Categories.AddCategoryMapping(17, NewznabStandardCategory.Books, "Misc / eBooks"); + caps.Categories.AddCategoryMapping(5, NewznabStandardCategory.PCMobileOther, "Misc / Mobile"); + caps.Categories.AddCategoryMapping(39, NewznabStandardCategory.Other, "Misc / Bildung"); + + caps.Categories.AddCategoryMapping(36, NewznabStandardCategory.TVForeign, "Englisch / Serien"); + caps.Categories.AddCategoryMapping(57, NewznabStandardCategory.TVForeign, "Englisch / Serienpacks"); + caps.Categories.AddCategoryMapping(37, NewznabStandardCategory.MoviesForeign, "Englisch / Filme"); + caps.Categories.AddCategoryMapping(47, NewznabStandardCategory.Books, "Englisch / eBooks"); + caps.Categories.AddCategoryMapping(48, NewznabStandardCategory.Other, "Englisch / Bildung"); + caps.Categories.AddCategoryMapping(49, NewznabStandardCategory.TVSport, "Englisch / Sport"); + + return caps; + } + } + + public class TorrentSyndikatRequestGenerator : IIndexerRequestGenerator + { + public TorrentSyndikatSettings Settings { get; set; } + public IndexerCapabilities Capabilities { get; set; } + + public TorrentSyndikatRequestGenerator() + { + } + + private IEnumerable GetPagedRequests(string term, int[] categories, string imdbId = null) + { + var searchString = term; + var queryCollection = new NameValueCollection { { "apikey", Settings.ApiKey } }; + + queryCollection.Add("limit", "50"); // Default 30 + queryCollection.Add("ponly", Settings.ProductsOnly ? "true" : "false"); + foreach (var releaseType in Settings.ReleaseTypes) + { + queryCollection.Add("release_type", ((TorrentSyndikatReleaseTypes)releaseType).ToString().Replace("_", "-")); + } + + if (imdbId != null) + { + queryCollection.Add("imdbId", imdbId); + } + else if (!string.IsNullOrWhiteSpace(searchString)) + { + // Suffix the first occurence of `s01` surrounded by whitespace with * + // That way we also search for single episodes in a whole season search + var regex = new Regex(@"(^|\s)(s\d{2})(\s|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase); + queryCollection.Add("searchstring", regex.Replace(searchString.Trim(), @"$1$2*$3")); + } + + var cats = string.Join(",", Capabilities.Categories.MapTorznabCapsToTrackers(categories)); + if (!string.IsNullOrEmpty(cats)) + { + queryCollection.Add("cats", cats); + } + + var searchUrl = string.Format("{0}/api_9djWe8Tb2NE3p6opyqnh/v1/browse.php?{1}", Settings.BaseUrl.TrimEnd('/'), queryCollection.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.ImdbId)); + + 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, searchCriteria.ImdbId)); + + 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> GetCookies { get; set; } + public Action, DateTime?> CookiesUpdater { get; set; } + } + + public class TorrentSyndikatParser : IParseIndexerResponse + { + private readonly TorrentSyndikatSettings _settings; + private readonly IndexerCapabilitiesCategories _categories; + + public TorrentSyndikatParser(TorrentSyndikatSettings settings, IndexerCapabilitiesCategories categories) + { + _settings = settings; + _categories = categories; + } + + public IList ParseResponse(IndexerResponse indexerResponse) + { + var torrentInfos = new List(); + + // TODO Deserialize to TorrentSyndikatResponse Type + var jsonContent = JObject.Parse(indexerResponse.Content); + + foreach (var row in jsonContent.Value("rows")) + { + var dateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); + + var id = row.Value("id"); + var details = _settings.BaseUrl + "details.php?id=" + id; + var seeders = row.Value("seeders"); + + var release = new TorrentInfo + { + MinimumRatio = 1, + MinimumSeedTime = 96 * 60 * 60, + DownloadVolumeFactor = 1, + UploadVolumeFactor = 1, + Guid = details, + InfoUrl = details, + DownloadUrl = _settings.BaseUrl + "download.php?id=" + id, + Title = row.Value("name"), + Categories = _categories.MapTrackerCatToNewznab(row.Value("category").ToString()), + PublishDate = dateTime.AddSeconds(row.Value("added")).ToLocalTime(), + Size = row.Value("size"), + Files = row.Value("numfiles"), + Seeders = seeders, + Peers = seeders + row.Value("leechers"), + Grabs = row.Value("snatched"), + ImdbId = row.Value("imdbId").GetValueOrDefault(), + TvdbId = row.Value("tvdbId").GetValueOrDefault(), + TmdbId = row.Value("tmdbId").GetValueOrDefault() + }; + + var poster = row.Value("poster"); + if (!string.IsNullOrWhiteSpace(poster)) + { + release.PosterUrl = _settings.BaseUrl + poster.Substring(1); + } + + var descriptions = new List(); + var title = row.Value("title"); + var titleOrigin = row.Value("title_origin"); + var year = row.Value("year"); + var pid = row.Value("pid"); + var releaseType = row.Value("release_type"); + var tags = row.Value("tags"); + var genres = row.Value("genres"); + + if (!string.IsNullOrWhiteSpace(title)) + { + descriptions.Add("Title: " + title); + } + + if (!string.IsNullOrWhiteSpace(titleOrigin)) + { + descriptions.Add("Original Title: " + titleOrigin); + } + + if (year > 0) + { + descriptions.Add("Year: " + year); + } + + if (pid > 0) + { + descriptions.Add("Product-Link: " + _settings.BaseUrl + "product.php?pid=" + pid); + } + + if (genres != null && genres.Any()) + { + descriptions.Add("Genres: " + string.Join(", ", genres)); + } + + if (tags != null && tags.Any()) + { + descriptions.Add("Tags: " + string.Join(", ", tags)); + } + + if (!string.IsNullOrWhiteSpace(releaseType)) + { + descriptions.Add("Release Type: " + releaseType); + } + + if (descriptions.Any()) + { + release.Description = string.Join(Environment.NewLine, descriptions); + } + + torrentInfos.Add(release); + } + + return torrentInfos.ToArray(); + } + + public Action, DateTime?> CookiesUpdater { get; set; } + } + + public class TorrentSyndikatSettingsValidator : AbstractValidator + { + public TorrentSyndikatSettingsValidator() + { + RuleFor(c => c.ApiKey).NotEmpty(); + } + } + + public class TorrentSyndikatSettings : IIndexerSettings + { + private static readonly TorrentSyndikatSettingsValidator Validator = new TorrentSyndikatSettingsValidator(); + + public TorrentSyndikatSettings() + { + ApiKey = ""; + ReleaseTypes = new List(); + } + + [FieldDefinition(1, Label = "Base Url", Type = FieldType.Select, SelectOptionsProviderAction = "getUrls", HelpText = "Select which baseurl Prowlarr will use for requests to the site")] + public string BaseUrl { get; set; } + + [FieldDefinition(2, Label = "API Key", Privacy = PrivacyLevel.ApiKey, HelpText = "Site API Key")] + public string ApiKey { get; set; } + + [FieldDefinition(3, Label = "Products Only", Type = FieldType.Checkbox, HelpText = "Limit search to torrents linked to a product")] + public bool ProductsOnly { get; set; } + + [FieldDefinition(4, Label = "Release Types", Type = FieldType.Select, SelectOptions = typeof(TorrentSyndikatReleaseTypes))] + public IEnumerable ReleaseTypes { get; set; } + + [FieldDefinition(5)] + public IndexerBaseSettings BaseSettings { get; set; } = new IndexerBaseSettings(); + + public NzbDroneValidationResult Validate() + { + return new NzbDroneValidationResult(Validator.Validate(this)); + } + } + + public enum TorrentSyndikatReleaseTypes + { + [FieldOption] + P2P = 1, + [FieldOption] + Scene = 2, + [FieldOption] + O_Scene = 3 + } +}