diff --git a/src/Jackett.Common/Indexers/MejorTorrent.cs b/src/Jackett.Common/Indexers/MejorTorrent.cs index 770ff5b4c..661625bf2 100644 --- a/src/Jackett.Common/Indexers/MejorTorrent.cs +++ b/src/Jackett.Common/Indexers/MejorTorrent.cs @@ -30,7 +30,8 @@ namespace Jackett.Common.Indexers link: WebUri.AbsoluteUri, caps: new TorznabCapabilities(TorznabCatType.TV, TorznabCatType.TVSD, - TorznabCatType.TVHD), + TorznabCatType.TVHD, + TorznabCatType.Movies), configService: configService, client: wc, logger: l, @@ -73,6 +74,7 @@ namespace Jackett.Common.Indexers query.SearchTerm = ""; } query.SearchTerm = query.SearchTerm.Replace("'", ""); + var requester = new MejorTorrentRequester(this); var tvShowScraper = new TvShowScraper(); var seasonScraper = new SeasonScraper(); @@ -81,17 +83,39 @@ namespace Jackett.Common.Indexers var downloadGenerator = new DownloadGenerator(requester, downloadScraper); var tvShowPerformer = new TvShowPerformer(requester, tvShowScraper, seasonScraper, downloadGenerator); var rssPerformer = new RssPerformer(requester, rssScraper, seasonScraper, downloadGenerator); + var movieSearchScraper = new MovieSearchScraper(); + var movieInfoScraper = new MovieInfoScraper(); + var movieDownloadScraper = new MovieDownloadScraper(); + var moviePerformer = new MoviePerformer(requester, movieSearchScraper, movieInfoScraper, movieDownloadScraper); + + var releases = new List(); if (string.IsNullOrEmpty(query.SanitizedSearchTerm)) { - var releases = await rssPerformer.PerformQuery(query); + releases = (await rssPerformer.PerformQuery(query)).ToList(); + var movie = releases.First(); + movie.Category.Add(TorznabCatType.Movies.ID); + releases.ToList().Add(movie); if (releases.Count() == 0) { - releases = await AliveCheck(tvShowPerformer); + releases = (await AliveCheck(tvShowPerformer)).ToList(); } return releases; } - return await tvShowPerformer.PerformQuery(query); + + if (query.Categories.Contains(TorznabCatType.Movies.ID) || query.Categories.Count() == 0) + { + releases.AddRange(await moviePerformer.PerformQuery(query)); + } + if (query.Categories.Contains(TorznabCatType.TV.ID) || + query.Categories.Contains(TorznabCatType.TVSD.ID) || + query.Categories.Contains(TorznabCatType.TVHD.ID) || + query.Categories.Count() == 0) + { + releases.AddRange(await tvShowPerformer.PerformQuery(query)); + } + + return releases; } private async Task> AliveCheck(TvShowPerformer tvShowPerformer) @@ -119,23 +143,23 @@ namespace Jackett.Common.Indexers T Extract(IHtmlDocument html); } - class RssScraper : IScraper>> + class RssScraper : IScraper>> { private readonly string LinkQuerySelector = "a[href*=\"/serie\"]"; - public IEnumerable> Extract(IHtmlDocument html) + public IEnumerable> Extract(IHtmlDocument html) { var episodes = GetNewEpisodesScratch(html); var links = GetLinks(html); - var results = new List>(); + var results = new List>(); for (var i = 0; i < episodes.Count(); i++) { - results.Add(new KeyValuePair(episodes.ElementAt(i), links.ElementAt(i))); + results.Add(new KeyValuePair(episodes.ElementAt(i), links.ElementAt(i))); } return results; } - private List GetNewEpisodesScratch(IHtmlDocument html) + private List GetNewEpisodesScratch(IHtmlDocument html) { var tvShowsElements = html.QuerySelectorAll(LinkQuerySelector); var seasonLinks = tvShowsElements.Select(e => e.Attributes["href"].Value); @@ -144,10 +168,10 @@ namespace Jackett.Common.Indexers var qualities = GetQualities(html); var seasonsFirstEpisodesAndLast = GetSeasonsFirstEpisodesAndLast(html); - var episodes = new List(); + var episodes = new List(); for(var i = 0; i < tvShowsElements.Count(); i++) { - var e = new MejorTorrentReleaseInfo(); + var e = new MTReleaseInfo(); e.TitleOriginal = titles.ElementAt(i); e.PublishDate = dates.ElementAt(i); e.CategoryText = qualities.ElementAt(i); @@ -300,9 +324,9 @@ namespace Jackett.Common.Indexers } } - class SeasonScraper : IScraper> + class SeasonScraper : IScraper> { - public IEnumerable Extract(IHtmlDocument html) + public IEnumerable Extract(IHtmlDocument html) { var episodesLinksHtml = html.QuerySelectorAll("a[href*=\"/serie-episodio-descargar-torrent\"]"); var episodesTexts = episodesLinksHtml.Select(l => l.TextContent).ToList(); @@ -314,7 +338,7 @@ namespace Jackett.Common.Indexers .Select(stringParts => new int[]{ Int32.Parse(stringParts[0]), Int32.Parse(stringParts[1]), Int32.Parse(stringParts[2]) }) .Select(intParts => new DateTime(intParts[0], intParts[1], intParts[2])); - var episodes = episodesLinks.Select(e => new MejorTorrentReleaseInfo()).ToList(); + var episodes = episodesLinks.Select(e => new MTReleaseInfo()).ToList(); for (var i = 0; i < episodes.Count(); i++) { @@ -326,7 +350,7 @@ namespace Jackett.Common.Indexers return episodes; } - private void GuessEpisodes(MejorTorrentReleaseInfo release, string episodeText) + private void GuessEpisodes(MTReleaseInfo release, string episodeText) { var seasonEpisodeRegex = new Regex(@"(\d{1,2}).*?(\d{1,2})", RegexOptions.IgnoreCase); var matchSeasonEpisode = seasonEpisodeRegex.Match(episodeText); @@ -350,7 +374,7 @@ namespace Jackett.Common.Indexers } } - private void ExtractLinkInfo(MejorTorrentReleaseInfo release, String link) + private void ExtractLinkInfo(MTReleaseInfo release, String link) { // LINK FORMAT: /serie-episodio-descargar-torrent-${ID}-${TITLE}-${SEASON_NUMBER}x${EPISODE_NUMBER}[range].html var regex = new Regex(@"\/serie-episodio-descargar-torrent-(\d+)-(.*)-(\d{1,2}).*(\d{1,2}).*\.html", RegexOptions.IgnoreCase); @@ -410,22 +434,23 @@ namespace Jackett.Common.Indexers } } - class MejorTorrentReleaseInfo : ReleaseInfo + class MTReleaseInfo : ReleaseInfo { public string MejorTorrentID; + public bool IsMovie; public int _season; public int _episodeNumber; private string _categoryText; private string _originalTitle; - public MejorTorrentReleaseInfo() + public MTReleaseInfo() { this.Category = new List(); this.Grabs = 5; this.Files = 1; this.PublishDate = new DateTime(); - this.Peers = 10; - this.Seeders = 10; + this.Peers = 1; + this.Seeders = 1; this.Size = ReleaseInfo.BytesFromGB(1); this._originalTitle = ""; } @@ -438,28 +463,36 @@ namespace Jackett.Common.Indexers get { return _categoryText; } set { - switch (value) + if (IsMovie) { - case "SDTV": - Category.Add(TorznabCatType.TVSD.ID); - _categoryText = "SDTV"; - break; - case "HDTV": - Category.Add(TorznabCatType.TVSD.ID); - _categoryText = "SDTV"; - break; - case "HDTV-720p": - Category.Add(TorznabCatType.TVHD.ID); - _categoryText = "HDTV-720p"; - break; - case "HDTV-1080p": - Category.Add(TorznabCatType.TVHD.ID); - _categoryText = "HDTV-1080p"; - break; - default: - Category.Add(TorznabCatType.TV.ID); - _categoryText = "HDTV-720p"; - break; + Category.Add(TorznabCatType.Movies.ID); + _categoryText = value; + } + else + { + switch (value) + { + case "SDTV": + Category.Add(TorznabCatType.TVSD.ID); + _categoryText = "SDTV"; + break; + case "HDTV": + Category.Add(TorznabCatType.TVSD.ID); + _categoryText = "SDTV"; + break; + case "HDTV-720p": + Category.Add(TorznabCatType.TVHD.ID); + _categoryText = "HDTV-720p"; + break; + case "HDTV-1080p": + Category.Add(TorznabCatType.TVHD.ID); + _categoryText = "HDTV-1080p"; + break; + default: + Category.Add(TorznabCatType.TV.ID); + _categoryText = "HDTV-720p"; + break; + } } TitleOriginal = _originalTitle; } @@ -475,15 +508,165 @@ namespace Jackett.Common.Indexers _originalTitle = value; if (_originalTitle != "") { - Title = _originalTitle.Replace(' ', '.'); + Title = _originalTitle; Title = char.ToUpper(Title[0]) + Title.Substring(1); } - var seasonAndEpisode = "S" + Season.ToString("00") + "E" + EpisodeNumber.ToString("00"); - if (Files > 1) + var seasonAndEpisode = ""; + if (!Category.Contains(TorznabCatType.Movies.ID)) { - seasonAndEpisode += "-" + FinalEpisodeNumber.ToString("00"); + seasonAndEpisode = "S" + Season.ToString("00") + "E" + EpisodeNumber.ToString("00"); + if (Files > 1) + { + seasonAndEpisode += "-" + FinalEpisodeNumber.ToString("00"); + } } - Title = String.Join(".", new List() { Title, seasonAndEpisode, CategoryText, "Spanish" }); + Title = String.Join(".", new List() { Title, seasonAndEpisode, CategoryText, "Spanish" }.Where(s => s != "")); + } + } + } + + class MoviePerformer : IPerformer + { + private IRequester requester; + private IScraper> movieSearchScraper; + private IScraper movieInfoScraper; + private IScraper movieDownloadScraper; + + public MoviePerformer( + IRequester requester, + IScraper> movieSearchScraper, + IScraper movieInfoScraper, + IScraper movieDownloadScraper) + { + this.requester = requester; + this.movieSearchScraper = movieSearchScraper; + this.movieInfoScraper = movieInfoScraper; + this.movieDownloadScraper = movieDownloadScraper; + } + + public async Task> PerformQuery(TorznabQuery query) + { + var uriSearch = CreateSearchUri(query.SearchTerm); + var htmlSearch = await requester.MakeRequest(uriSearch); + var moviesInfoUris = movieSearchScraper.Extract(htmlSearch); + var infoHtmlTasks = moviesInfoUris.Select(async u => await requester.MakeRequest(u)); + var infoHtmls = await Task.WhenAll(infoHtmlTasks); + var movies = infoHtmls.Select(h => movieInfoScraper.Extract(h)); + + var tasks = movies.Select(async m => + { + var html = await requester.MakeRequest(m.Link); + return new KeyValuePair(m, html); + }); + var moviesWithHtml = await Task.WhenAll(tasks.ToArray()); + movies = moviesWithHtml.Select(movieWithHtml => + { + var movie = movieWithHtml.Key; + var html = movieWithHtml.Value; + movie.Link = movieDownloadScraper.Extract(html); + movie.Guid = movieWithHtml.Key.Link; + return movie; + }); + + return movies; + } + } + + class MovieSearchScraper : IScraper> + { + public IEnumerable Extract(IHtmlDocument html) + { + return html.QuerySelectorAll("a[href*=\"/peli-\"]") + .Select(e => e.GetAttribute("href")) + .Select(relativeUri => new Uri(WebUri, relativeUri)); + } + } + + class MovieInfoScraper : IScraper + { + public MTReleaseInfo Extract(IHtmlDocument html) + { + var release = new MTReleaseInfo(); + release.IsMovie = true; + var selectors = html.QuerySelectorAll("b"); + var titleSelector = html.QuerySelector("span>b"); + try + { + var title = titleSelector.TextContent; + if (title.Contains("(")) + { + title = title.Substring(0, title.IndexOf("(")).Trim(); + } + release.TitleOriginal = title; + } + catch { } + try + { + var year = selectors.Where(s => s.TextContent.ToLower().Contains("año")) + .First().NextSibling.TextContent.Trim(); + release.TitleOriginal += " (" + year + ")"; + } catch { } + try + { + var dateStr = selectors.Where(s => s.TextContent.ToLower().Contains("fecha")) + .First().NextSibling.TextContent.Trim(); + var date = Convert.ToDateTime(dateStr); + release.PublishDate = date; + } catch { } + try + { + var sizeStr = selectors.Where(s => s.TextContent.ToLower().Contains("tamaño")) + .First().NextSibling.TextContent.Trim(); + Regex rgx = new Regex(@"[^0-9,.]"); + long size; + if (sizeStr.ToLower().Trim().EndsWith("mb")) + { + size = ReleaseInfo.BytesFromMB(float.Parse(rgx.Replace(sizeStr, ""))); + } + else + { + sizeStr = rgx.Replace(sizeStr, "").Replace(",", "."); + size = ReleaseInfo.BytesFromGB(float.Parse(rgx.Replace(sizeStr, ""))); + } + release.Size = size; + } catch { } + try + { + var category = selectors.Where(s => s.TextContent.ToLower().Contains("formato")) + .First().NextSibling.TextContent.Trim(); + release.CategoryText = category; + } catch { } + try + { + var title = titleSelector.TextContent; + if (title.Contains("(") && title.Contains(")") && title.Contains("4k")) + { + release.CategoryText = "2160p"; + } + } + catch { } + try + { + var link = html.QuerySelector("a[href*=\"sec=descargas\"]").GetAttribute("href"); + release.Link = new Uri(WebUri, link); + release.Guid = release.Link; + } + catch { } + return release; + } + } + + class MovieDownloadScraper : IScraper + { + public Uri Extract(IHtmlDocument html) + { + try + { + return new Uri(WebUri, html.QuerySelector("a[href*=\".torrent\"]").GetAttribute("href")); + } + catch + { + return null; } } } @@ -553,14 +736,14 @@ namespace Jackett.Common.Indexers class RssPerformer : IPerformer { private IRequester requester; - private IScraper>> rssScraper; - private IScraper> seasonScraper; + private IScraper>> rssScraper; + private IScraper> seasonScraper; private IDownloadGenerator downloadGenerator; public RssPerformer( IRequester requester, - IScraper>> rssScraper, - IScraper> seasonScraper, + IScraper>> rssScraper, + IScraper> seasonScraper, IDownloadGenerator downloadGenerator) { this.requester = requester; @@ -586,7 +769,7 @@ namespace Jackett.Common.Indexers return episodes; } - private async Task AddMejorTorrentIDs(MejorTorrentReleaseInfo episode, Uri seasonUri) + private async Task AddMejorTorrentIDs(MTReleaseInfo episode, Uri seasonUri) { var html = await requester.MakeRequest(seasonUri); var newEpisodes = seasonScraper.Extract(html); @@ -605,13 +788,13 @@ namespace Jackett.Common.Indexers { private IRequester requester; private IScraper> tvShowScraper; - private IScraper> seasonScraper; + private IScraper> seasonScraper; private IDownloadGenerator downloadGenerator; public TvShowPerformer( IRequester requester, IScraper> tvShowScraper, - IScraper> seasonScraper, + IScraper> seasonScraper, IDownloadGenerator downloadGenerator) { this.requester = requester; @@ -668,7 +851,7 @@ namespace Jackett.Common.Indexers return seasons.ToList(); } - private async Task> GetEpisodes(TorznabQuery query, IEnumerable seasons) + private async Task> GetEpisodes(TorznabQuery query, IEnumerable seasons) { var episodesHtmlTasks = new Dictionary>(); seasons.ToList().ForEach(season => @@ -698,7 +881,7 @@ namespace Jackett.Common.Indexers interface IDownloadGenerator { - Task AddDownloadLinks(IEnumerable episodes); + Task AddDownloadLinks(IEnumerable episodes); } class DownloadGenerator : IDownloadGenerator @@ -712,7 +895,7 @@ namespace Jackett.Common.Indexers this.downloadScraper = downloadScraper; } - public async Task AddDownloadLinks(IEnumerable episodes) + public async Task AddDownloadLinks(IEnumerable episodes) { var downloadRequester = new MejorTorrentDownloadRequesterDecorator(requester); var downloadHtml = await downloadRequester.MakeRequest(episodes.Select(e => e.MejorTorrentID));