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