diff --git a/README.md b/README.md
index 01de7cdb1..88a8f370c 100644
--- a/README.md
+++ b/README.md
@@ -64,6 +64,7 @@ Developer note: The software implements the [Torznab](https://github.com/Sonarr/
* TransmitheNet
* TV Chaos UK
* World-In-HD
+ * x264
* XSpeeds
* Xthor
diff --git a/src/Jackett/Content/custom.js b/src/Jackett/Content/custom.js
index 1c9cd6572..c18e7f5e8 100644
--- a/src/Jackett/Content/custom.js
+++ b/src/Jackett/Content/custom.js
@@ -200,9 +200,35 @@ function populateConfigItems(configForm, config) {
var template = setupItemTemplate(item);
$formItemContainer.append(template);
if (item.type === 'recaptcha') {
- grecaptcha.render($('.jackettrecaptcha')[0], {
- 'sitekey': item.sitekey
- });
+ var jackettrecaptcha = $('.jackettrecaptcha');
+ jackettrecaptcha.data("version", item.version);
+ switch (item.version) {
+ case "1":
+ // The v1 reCAPTCHA code uses document.write() calls to write the CAPTCHA to the location where the script was loaded.
+ // As it's loaded async this doesn't work.
+ // We use an iframe to work around this problem.
+ var html = '';
+ var frame = document.createElement('iframe');
+ frame.id = "jackettrecaptchaiframe";
+ frame.style.height = "145px";
+ frame.style.weight = "326px";
+ frame.style.border = "none";
+ frame.onload = function () {
+ // auto resize iframe to content
+ frame.style.height = frame.contentWindow.document.body.scrollHeight + 'px';
+ frame.style.width = frame.contentWindow.document.body.scrollWidth + 'px';
+ }
+ jackettrecaptcha.append(frame);
+ frame.contentDocument.open();
+ frame.contentDocument.write(html);
+ frame.contentDocument.close();
+ break;
+ case "2":
+ grecaptcha.render(jackettrecaptcha[0], {
+ 'sitekey': item.sitekey
+ });
+ break;
+ }
}
}
}
@@ -235,7 +261,18 @@ function getConfigModalJson(configForm) {
break;
case "recaptcha":
if (window.jackettIsLocal) {
- itemEntry.value = $('.g-recaptcha-response').val();
+ var version = $el.find('.jackettrecaptcha').data("version");
+ switch (version) {
+ case "1":
+ var frameDoc = $("#jackettrecaptchaiframe")[0].contentDocument;
+ itemEntry.version = version;
+ itemEntry.challenge = $("#recaptcha_challenge_field", frameDoc).val()
+ itemEntry.value = $("#recaptcha_response_field", frameDoc).val()
+ break;
+ case "2":
+ itemEntry.value = $('.g-recaptcha-response').val();
+ break;
+ }
} else {
itemEntry.cookie = $el.find(".setup-item-recaptcha input").val();
}
diff --git a/src/Jackett/Content/logos/x264.png b/src/Jackett/Content/logos/x264.png
new file mode 100644
index 000000000..c48002c90
Binary files /dev/null and b/src/Jackett/Content/logos/x264.png differ
diff --git a/src/Jackett/Indexers/x264.cs b/src/Jackett/Indexers/x264.cs
new file mode 100644
index 000000000..584a42e22
--- /dev/null
+++ b/src/Jackett/Indexers/x264.cs
@@ -0,0 +1,180 @@
+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.Linq;
+using System.Threading.Tasks;
+using Jackett.Models.IndexerConfig;
+using System.Collections.Specialized;
+
+namespace Jackett.Indexers
+{
+ public class x264 : BaseIndexer, IIndexer
+ {
+ private string SearchUrl { get { return SiteLink + "browse.php"; } }
+ private string LoginUrl { get { return SiteLink + "login.php"; } }
+ private string SubmitLoginUrl { get { return SiteLink + "takelogin.php"; } }
+
+ new ConfigurationDataRecaptchaLogin configData
+ {
+ get { return (ConfigurationDataRecaptchaLogin)base.configData; }
+ set { base.configData = value; }
+ }
+
+ public x264(IIndexerManagerService i, Logger l, IWebClient w, IProtectionService ps)
+ : base(name: "x264",
+ description: "A movie/TV tracker",
+ link: "https://x264.me/",
+ caps: new TorznabCapabilities(),
+ manager: i,
+ client: w,
+ logger: l,
+ p: ps,
+ configData: new ConfigurationDataRecaptchaLogin())
+ {
+ AddCategoryMapping(20, TorznabCatType.Movies); // Movies&TV/Sources
+ AddCategoryMapping(53, TorznabCatType.MoviesHD); // Movies/1080p
+ AddCategoryMapping(30, TorznabCatType.MoviesHD); // Movies/576p
+ AddCategoryMapping(50, TorznabCatType.MoviesHD); // Movies/720p
+ AddCategoryMapping(33, TorznabCatType.MoviesSD); // Movies/SD
+ AddCategoryMapping(54, TorznabCatType.TVHD); // TV/1080p
+ AddCategoryMapping(31, TorznabCatType.TVHD); // TV/576p
+ AddCategoryMapping(51, TorznabCatType.TVHD); // TV/720p
+ AddCategoryMapping(25, TorznabCatType.TVSD); // TV/SD
+ }
+
+ public override async Task GetConfigurationForSetup()
+ {
+ var loginPage = await RequestStringWithCookies(LoginUrl, string.Empty);
+ CQ dom = loginPage.Content;
+ CQ recaptchaScript = dom.Find("script").First();
+
+ string recaptchaSiteKey = recaptchaScript.Attr("src").Split('=')[1];
+ var result = new ConfigurationDataRecaptchaLogin();
+ result.CookieHeader.Value = loginPage.Cookies;
+ result.Captcha.SiteKey = recaptchaSiteKey;
+ result.Captcha.Version = "1";
+ return result;
+ }
+
+ public async Task ApplyConfiguration(JToken configJson)
+ {
+ configData.LoadValuesFromJson(configJson);
+ var pairs = new Dictionary {
+ { "username", configData.Username.Value },
+ { "password", configData.Password.Value },
+ { "recaptcha_challenge_field", configData.Captcha.Challenge },
+ { "recaptcha_response_field", configData.Captcha.Value },
+ };
+
+ if (!string.IsNullOrWhiteSpace(configData.Captcha.Cookie))
+ {
+ // Cookie was manually supplied
+ CookieHeader = configData.Captcha.Cookie;
+ try
+ {
+ var results = await PerformQuery(new TorznabQuery());
+ if (!results.Any())
+ {
+ throw new Exception("Your cookie did not work");
+ }
+
+ SaveConfig();
+ IsConfigured = true;
+ return IndexerConfigurationStatus.Completed;
+ }
+ catch (Exception e)
+ {
+ IsConfigured = false;
+ throw new Exception("Your cookie did not work: " + e.Message);
+ }
+ }
+
+ var result = await RequestLoginAndFollowRedirect(SubmitLoginUrl, pairs, configData.CookieHeader.Value, true, null, LoginUrl);
+ await ConfigureIfOK(result.Cookies, result.Content.Contains("logout.php"), () =>
+ {
+ var errorMessage = result.Content;
+ throw new ExceptionWithConfigData(errorMessage, configData);
+ });
+ return IndexerConfigurationStatus.RequiresTesting;
+ }
+
+ public async Task> PerformQuery(TorznabQuery query)
+ {
+ List releases = new List();
+
+ var searchString = query.GetQueryString();
+ var searchUrl = SearchUrl;
+ var queryCollection = new NameValueCollection();
+ queryCollection.Add("incldead", "1");
+ queryCollection.Add("xtype", "0");
+ queryCollection.Add("stype", "0");
+
+ if (!string.IsNullOrWhiteSpace(searchString))
+ {
+ queryCollection.Add("search", searchString);
+ }
+
+ foreach (var cat in MapTorznabCapsToTrackers(query))
+ {
+ queryCollection.Add("c" + cat, "1");
+ }
+
+ searchUrl += "?" + queryCollection.GetQueryString();
+
+ var results = await RequestStringWithCookiesAndRetry(searchUrl);
+ try
+ {
+ CQ dom = results.Content;
+ var rows = dom["table > tbody > tr[height=36]"];
+ foreach (var row in rows)
+ {
+ var release = new ReleaseInfo();
+ release.MinimumRatio = 1;
+ release.MinimumSeedTime = 7 * 24 * 60 * 60;
+
+ var qRow = row.Cq();
+ var qCatLink = qRow.Find("a[href^=?cat]").First();
+ var qDetailsLink = qRow.Find("a[href^=details.php]").First();
+ var qSeeders = qRow.Find("td:eq(8)");
+ var qLeechers = qRow.Find("td:eq(9)");
+ var qDownloadLink = qRow.Find("a[href^=\"download.php\"]").First();
+ var qImdbLink = qRow.Find("a[href^=/redir.php?url=http://www.imdb.com]");
+ var qSize = qRow.Find("td:eq(6)");
+
+ var catStr = qCatLink.Attr("href").Split('=')[1];
+ release.Category = MapTrackerCatToNewznab(catStr);
+
+ release.Link = new Uri(SiteLink + qDownloadLink.Attr("href"));
+ release.Title = qDetailsLink.Text().Trim();
+ release.Comments = new Uri(SiteLink + qDetailsLink.Attr("href"));
+ release.Guid = release.Link;
+
+ var sizeStr = qSize.Text();
+ release.Size = ReleaseInfo.GetBytes(sizeStr);
+
+ if(qImdbLink.Length == 1) {
+ var ImdbId = qImdbLink.Attr("href").Split('/').Last().Substring(2);
+ release.Imdb = ParseUtil.CoerceLong(ImdbId);
+ }
+
+ release.Seeders = ParseUtil.CoerceInt(qSeeders.Text());
+ release.Peers = ParseUtil.CoerceInt(qLeechers.Text()) + release.Seeders;
+
+ 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 39a51f6f9..7a341ff80 100644
--- a/src/Jackett/Jackett.csproj
+++ b/src/Jackett/Jackett.csproj
@@ -163,6 +163,7 @@
+
@@ -437,6 +438,9 @@
PreserveNewest
+
+ PreserveNewest
+
PreserveNewest
diff --git a/src/Jackett/Models/IndexerConfig/ConfigurationData.cs b/src/Jackett/Models/IndexerConfig/ConfigurationData.cs
index 134968542..ce3479ed7 100644
--- a/src/Jackett/Models/IndexerConfig/ConfigurationData.cs
+++ b/src/Jackett/Models/IndexerConfig/ConfigurationData.cs
@@ -73,6 +73,8 @@ namespace Jackett.Models.IndexerConfig
case ItemType.Recaptcha:
((RecaptchaItem)item).Value = arrItem.Value("value");
((RecaptchaItem)item).Cookie = arrItem.Value("cookie");
+ ((RecaptchaItem)item).Version = arrItem.Value("version");
+ ((RecaptchaItem)item).Challenge = arrItem.Value("challenge");
break;
}
}
@@ -92,6 +94,7 @@ namespace Jackett.Models.IndexerConfig
{
case ItemType.Recaptcha:
jObject["sitekey"] = ((RecaptchaItem)item).SiteKey;
+ jObject["version"] = ((RecaptchaItem)item).Version;
break;
case ItemType.InputString:
case ItemType.HiddenData:
@@ -177,8 +180,11 @@ namespace Jackett.Models.IndexerConfig
public class RecaptchaItem : StringItem
{
+ public string Version { get; set; }
+ public string Challenge { get; set; }
public RecaptchaItem()
{
+ this.Version = "2";
ItemType = ConfigurationData.ItemType.Recaptcha;
}
}