diff --git a/README.md b/README.md index b414557f3..ceae550db 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,7 @@ Developer note: The software implements the [Torznab](https://github.com/Sonarr/ * ILoveTorrents * Immortalseed * IPTorrents + * PassThePopcorn * MoreThanTV * MyAnonamouse * NCore diff --git a/src/Jackett/Content/logos/passthepopcorn.png b/src/Jackett/Content/logos/passthepopcorn.png new file mode 100644 index 000000000..dd5bab04e Binary files /dev/null and b/src/Jackett/Content/logos/passthepopcorn.png differ diff --git a/src/Jackett/Indexers/PassThePopcorn.cs b/src/Jackett/Indexers/PassThePopcorn.cs new file mode 100644 index 000000000..2d58b1caa --- /dev/null +++ b/src/Jackett/Indexers/PassThePopcorn.cs @@ -0,0 +1,167 @@ +using CsQuery; +using Jackett.Models; +using Jackett.Services; +using Jackett.Utils; +using Jackett.Utils.Clients; +using Newtonsoft.Json.Linq; +using NLog; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Threading.Tasks; +using System.Web; +using Jackett.Models.IndexerConfig; + +namespace Jackett.Indexers +{ + public class PassThePopcorn : BaseIndexer, IIndexer + { + private string LoginUrl { get { return "https://tls.passthepopcorn.me/ajax.php?action=login"; } } + private string indexUrl { get { return "https://tls.passthepopcorn.me/ajax.php?action=login"; } } + private string SearchUrl { get { return "https://tls.passthepopcorn.me/torrents.php"; } } + private string DetailURL { get { return "https://tls.passthepopcorn.me/torrents.php?torrentid="; } } + private string AuthKey { get; set; } + new ConfigurationDataBasicLoginWithFilterAndPasskey configData + { + get { return (ConfigurationDataBasicLoginWithFilterAndPasskey)base.configData; } + set { base.configData = value; } + } + + public PassThePopcorn(IIndexerManagerService i, Logger l, IWebClient c, IProtectionService ps) + : base(name: "PassThePopcorn", + description: "PassThePopcorn", + link: "https://passthepopcorn.me/", + caps: new TorznabCapabilities(), + manager: i, + client: c, + logger: l, + p: ps, + configData: new ConfigurationDataBasicLoginWithFilterAndPasskey(@"Enter filter options below to restrict search results. + Separate options with a space if using more than one option.
Filter options available: +
GoldenPopcorn
Scene
Checked")) + { + AddCategoryMapping(1, TorznabCatType.Movies); + AddCategoryMapping(1, TorznabCatType.MoviesForeign); + AddCategoryMapping(1, TorznabCatType.MoviesOther); + AddCategoryMapping(1, TorznabCatType.MoviesSD); + AddCategoryMapping(1, TorznabCatType.MoviesHD); + AddCategoryMapping(1, TorznabCatType.Movies3D); + AddCategoryMapping(1, TorznabCatType.MoviesBluRay); + AddCategoryMapping(1, TorznabCatType.MoviesDVD); + AddCategoryMapping(1, TorznabCatType.MoviesWEBDL); + } + + public async Task ApplyConfiguration(JToken configJson) + { + configData.LoadValuesFromJson(configJson); + + await DoLogin(); + + return IndexerConfigurationStatus.RequiresTesting; + } + + private async Task DoLogin() + { + var pairs = new Dictionary { + { "username", configData.Username.Value }, + { "password", configData.Password.Value }, + { "passkey", configData.Passkey.Value }, + { "keeplogged", "1" }, + { "login", "Log In!" } + }; + + var response = await RequestLoginAndFollowRedirect(LoginUrl, pairs, null, true, indexUrl, SiteLink); + JObject js_response = JObject.Parse(response.Content); + await ConfigureIfOK(response.Cookies, response.Content != null && (string)js_response["Result"] != "Error", () => + { + // Landing page wil have "Result":"Error" if log in fails + string errorMessage = (string)js_response["Message"]; + throw new ExceptionWithConfigData(errorMessage, configData); + }); + } + + public async Task> PerformQuery(TorznabQuery query) + { + await DoLogin(); + + var releases = new List(); + bool configGoldenPopcornOnly = configData.FilterString.Value.ToLowerInvariant().Contains("goldenpopcorn"); + bool configSceneOnly = configData.FilterString.Value.ToLowerInvariant().Contains("scene"); + bool configCheckedOnly = configData.FilterString.Value.ToLowerInvariant().Contains("checked"); + string movieListSearchUrl; + + if (string.IsNullOrEmpty(query.GetQueryString())) + movieListSearchUrl = string.Format("{0}?json=noredirect", SearchUrl); + else + { + if (!string.IsNullOrEmpty(query.ImdbID)) + { + movieListSearchUrl = string.Format("{0}?json=noredirect&searchstr={1}", SearchUrl, HttpUtility.UrlEncode(query.ImdbID)); + } + else + { + movieListSearchUrl = string.Format("{0}?json=noredirect&searchstr={1}", SearchUrl, HttpUtility.UrlEncode(query.GetQueryString())); + } + } + + var results = await RequestStringWithCookiesAndRetry(movieListSearchUrl); + try + { + //Iterate over the releases for each movie + JObject js_results = JObject.Parse(results.Content); + foreach (var movie in js_results["Movies"]) + { + string movie_title = (string) movie["Title"]; + long movie_imdbid = long.Parse((string)movie["ImdbId"]); + string movie_groupid = (string)movie["GroupId"]; + foreach (var torrent in movie["Torrents"]) + { + var release = new ReleaseInfo(); + release.Title = movie_title; + release.Description = release.Title; + release.Imdb = movie_imdbid; + release.Comments = new Uri(string.Format("{0}?id={1}", SearchUrl, HttpUtility.UrlEncode(movie_groupid))); + release.Guid = release.Comments; + release.Size = long.Parse((string)torrent["Size"]); + release.Seeders = int.Parse((string)torrent["Seeders"]); + release.Peers = int.Parse((string)torrent["Leechers"]); + release.PublishDate = DateTime.ParseExact((string)torrent["UploadTime"], "yyyy-MM-dd HH:mm:ss", + CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal).ToLocalTime(); + release.Link = new Uri(string.Format("{0}?action=download&id={1}&authkey={2}&torrent_pass={3}", + SearchUrl, HttpUtility.UrlEncode((string)torrent["Id"]), HttpUtility.UrlEncode(AuthKey), HttpUtility.UrlEncode(configData.Passkey.Value))); + release.MinimumRatio = 1; + release.MinimumSeedTime = 345600; + release.Category = 2000; + + bool golden, scene, check; + bool.TryParse((string)torrent["GoldenPopcorn"], out golden); + bool.TryParse((string)torrent["Scene"], out scene); + bool.TryParse((string)torrent["Checked"], out check); + + + if (configGoldenPopcornOnly && !golden) + { + continue; //Skip release if user only wants GoldenPopcorn + } + if (configSceneOnly && !scene) + { + continue; //Skip release if user only wants Scene + } + if (configCheckedOnly && !check) + { + continue; //Skip release if user only wants Checked + } + releases.Add(release); + } + } + } + catch (Exception ex) + { + OnParseError(results.Content, ex); + } + + return releases; + } + } +} diff --git a/src/Jackett/Jackett.csproj b/src/Jackett/Jackett.csproj index 0f48acebf..78f854b46 100644 --- a/src/Jackett/Jackett.csproj +++ b/src/Jackett/Jackett.csproj @@ -187,6 +187,7 @@ + @@ -229,6 +230,7 @@ + diff --git a/src/Jackett/Models/IndexerConfig/ConfigurationDataBasicLoginWithFilterAndPasskey.cs b/src/Jackett/Models/IndexerConfig/ConfigurationDataBasicLoginWithFilterAndPasskey.cs new file mode 100644 index 000000000..87320f983 --- /dev/null +++ b/src/Jackett/Models/IndexerConfig/ConfigurationDataBasicLoginWithFilterAndPasskey.cs @@ -0,0 +1,32 @@ +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Jackett.Models.IndexerConfig +{ + public class ConfigurationDataBasicLoginWithFilterAndPasskey : ConfigurationData + { + public StringItem Username { get; private set; } + public StringItem Password { get; private set; } + public StringItem Passkey { get; private set; } + public DisplayItem FilterExample { get; private set; } + public StringItem FilterString { get; private set; } + + public ConfigurationDataBasicLoginWithFilterAndPasskey(string FilterInstructions) + { + Username = new StringItem { Name = "Username" }; + Password = new StringItem { Name = "Password" }; + Passkey = new StringItem { Name = "Passkey" }; + FilterExample = new DisplayItem(FilterInstructions) + { + Name = "" + }; + FilterString = new StringItem { Name = "Filters (optional)" }; + } + + + } +} \ No newline at end of file