using System; using System.Collections.Generic; using System.Collections.Specialized; using System.Globalization; using System.Text; using FluentValidation; 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.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 { [Obsolete] public class YTS : TorrentIndexerBase { public override string Name => "YTS"; public override string[] IndexerUrls => new string[] { "https://yts.mx/" }; public override string Language => "en-US"; public override string Description => "YTS is a Public torrent site specialising in HD movies of small size"; public override Encoding Encoding => Encoding.GetEncoding("windows-1252"); public override DownloadProtocol Protocol => DownloadProtocol.Torrent; public override IndexerPrivacy Privacy => IndexerPrivacy.Public; public override TimeSpan RateLimit => TimeSpan.FromSeconds(2.5); public override IndexerCapabilities Capabilities => SetCapabilities(); public YTS(IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger) : base(httpClient, eventAggregator, indexerStatusService, configService, logger) { } public override IIndexerRequestGenerator GetRequestGenerator() { return new YTSRequestGenerator() { Settings = Settings, Capabilities = Capabilities }; } public override IParseIndexerResponse GetParser() { return new YTSParser(Settings, Capabilities.Categories); } private IndexerCapabilities SetCapabilities() { var caps = new IndexerCapabilities { TvSearchParams = new List { }, MovieSearchParams = new List { MovieSearchParam.Q, MovieSearchParam.ImdbId }, MusicSearchParams = new List { }, BookSearchParams = new List { } }; caps.Categories.AddCategoryMapping(45, NewznabStandardCategory.MoviesHD, "Movies/x264/720p"); caps.Categories.AddCategoryMapping(44, NewznabStandardCategory.MoviesHD, "Movies/x264/1080p"); caps.Categories.AddCategoryMapping(46, NewznabStandardCategory.MoviesUHD, "Movies/x264/2160p"); caps.Categories.AddCategoryMapping(47, NewznabStandardCategory.Movies3D, "Movies/x264/3D"); return caps; } } public class YTSRequestGenerator : IIndexerRequestGenerator { public YTSSettings Settings { get; set; } public IndexerCapabilities Capabilities { get; set; } public YTSRequestGenerator() { } private IEnumerable GetPagedRequests(string term, int[] categories, string imdbId = null) { var searchUrl = string.Format("{0}/api/v2/list_movies.json", Settings.BaseUrl.TrimEnd('/')); var searchString = term; var queryCollection = new NameValueCollection { // without this the API sometimes returns nothing { "sort", "date_added" }, { "limit", "50" } }; if (imdbId.IsNotNullOrWhiteSpace()) { queryCollection.Add("query_term", imdbId); } else if (!string.IsNullOrWhiteSpace(searchString)) { searchString = searchString.Replace("'", ""); // ignore ' (e.g. search for america's Next Top Model) queryCollection.Add("query_term", searchString); } searchUrl = searchUrl + "?" + 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.FullImdbId)); return pageableRequests; } public IndexerPageableRequestChain GetSearchRequests(MusicSearchCriteria searchCriteria) { var pageableRequests = new IndexerPageableRequestChain(); return pageableRequests; } public IndexerPageableRequestChain GetSearchRequests(TvSearchCriteria searchCriteria) { var pageableRequests = new IndexerPageableRequestChain(); 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> GetCookies { get; set; } public Action, DateTime?> CookiesUpdater { get; set; } } public class YTSParser : IParseIndexerResponse { private readonly YTSSettings _settings; private readonly IndexerCapabilitiesCategories _categories; public YTSParser(YTSSettings settings, IndexerCapabilitiesCategories categories) { _settings = settings; _categories = categories; } public IList ParseResponse(IndexerResponse indexerResponse) { var torrentInfos = new List(); var contentString = indexerResponse.Content; // returned content might start with an html error message, remove it first var jsonStart = contentString.IndexOf('{'); var jsonContentStr = contentString.Remove(0, jsonStart); var jsonContent = JObject.Parse(jsonContentStr); var result = jsonContent.Value("status"); // query was not successful if (result != "ok") { return new List(); } var dataItems = jsonContent.Value("data"); var movieCount = dataItems.Value("movie_count"); // no results found in query if (movieCount < 1) { return new List(); } var movies = dataItems.Value("movies"); if (movies == null) { return new List(); } foreach (var movie in movies) { var torrents = movie.Value("torrents"); if (torrents == null) { continue; } foreach (var torrent in torrents) { var release = new TorrentInfo(); // append type: BRRip or WEBRip, resolves #3558 via #4577 var type = torrent.Value("type"); switch (type) { case "web": type = "WEBRip"; break; default: type = "BRRip"; break; } var quality = torrent.Value("quality"); var title = movie.Value("title").Replace(":", "").Replace(' ', '.'); var year = movie.Value("year"); release.Title = $"{title}.{year}.{quality}.{type}-YTS"; var imdb = movie.Value("imdb_code"); release.ImdbId = ParseUtil.GetImdbID(imdb).Value; release.InfoHash = torrent.Value("hash"); // magnet link is auto generated from infohash // ex: 2015-08-16 21:25:08 +0000 var dateStr = torrent.Value("date_uploaded"); var dateTime = DateTime.ParseExact(dateStr, "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture); release.PublishDate = DateTime.SpecifyKind(dateTime, DateTimeKind.Utc).ToLocalTime(); release.DownloadUrl = torrent.Value("url"); release.Seeders = torrent.Value("seeds"); release.Peers = torrent.Value("peers") + release.Seeders; release.Size = torrent.Value("size_bytes"); release.DownloadVolumeFactor = 0; release.UploadVolumeFactor = 1; release.InfoUrl = movie.Value("url"); release.PosterUrl = new Uri(movie.Value("large_cover_image")).AbsoluteUri; release.Guid = release.DownloadUrl; // map the quality to a newznab category for torznab compatibility (for Radarr, etc) switch (quality) { case "720p": release.Categories = _categories.MapTrackerCatToNewznab("45"); break; case "1080p": release.Categories = _categories.MapTrackerCatToNewznab("44"); break; case "2160p": release.Categories = _categories.MapTrackerCatToNewznab("46"); break; case "3D": release.Categories = _categories.MapTrackerCatToNewznab("47"); break; default: release.Categories = _categories.MapTrackerCatToNewznab("45"); break; } if (release == null) { continue; } torrentInfos.Add(release); } } return torrentInfos.ToArray(); } public Action, DateTime?> CookiesUpdater { get; set; } } public class YTSSettingsValidator : AbstractValidator { } public class YTSSettings : IIndexerSettings { private static readonly YTSSettingsValidator Validator = new YTSSettingsValidator(); [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)] public IndexerBaseSettings BaseSettings { get; set; } = new IndexerBaseSettings(); public NzbDroneValidationResult Validate() { return new NzbDroneValidationResult(Validator.Validate(this)); } } }